diff --git a/cmd/golangorg/server.go b/cmd/golangorg/server.go index 88c8d32a1b..239831a73a 100644 --- a/cmd/golangorg/server.go +++ b/cmd/golangorg/server.go @@ -14,6 +14,8 @@ import ( "errors" "flag" "fmt" + "go/format" + "html/template" "io/fs" "io/ioutil" "log" @@ -31,8 +33,6 @@ import ( "cloud.google.com/go/datastore" "golang.org/x/build/repos" "golang.org/x/website" - "golang.org/x/website/internal/backport/go/format" - "golang.org/x/website/internal/backport/html/template" "golang.org/x/website/internal/blog" "golang.org/x/website/internal/codewalk" "golang.org/x/website/internal/dl" diff --git a/cmd/googlegolangorg/main.go b/cmd/googlegolangorg/main.go index d53d1ee8b9..e143a43ffc 100644 --- a/cmd/googlegolangorg/main.go +++ b/cmd/googlegolangorg/main.go @@ -3,11 +3,10 @@ package main import ( "fmt" + "html/template" "net/http" "os" "strings" - - "golang.org/x/website/internal/backport/html/template" ) var repoMap = map[string]*repoImport{ diff --git a/internal/api/api_test.go b/internal/api/api_test.go index 79286278a0..98b1dd47e6 100644 --- a/internal/api/api_test.go +++ b/internal/api/api_test.go @@ -5,11 +5,10 @@ package api import ( + "go/build" "os" "runtime" "testing" - - "golang.org/x/website/internal/backport/go/build" ) func TestParseVersionRow(t *testing.T) { diff --git a/internal/backport/README.md b/internal/backport/README.md deleted file mode 100644 index 92c80ad95f..0000000000 --- a/internal/backport/README.md +++ /dev/null @@ -1,10 +0,0 @@ -This directory contains backports of upcoming packages from the -standard library, beyond the Go version supplied by the latest Google -App Engine. - -The procedure for adding a backport is fairly manual: copy the files -to a new directory and then global search and replace to modify import -paths to point to the backport. - -As new versions of Go land on Google App Engine, this directory should -be pruned as much as possible. diff --git a/internal/backport/diff/diff.go b/internal/backport/diff/diff.go deleted file mode 100644 index 47b2856714..0000000000 --- a/internal/backport/diff/diff.go +++ /dev/null @@ -1,261 +0,0 @@ -// Copyright 2022 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 diff - -import ( - "bytes" - "fmt" - "sort" - "strings" -) - -// A pair is a pair of values tracked for both the x and y side of a diff. -// It is typically a pair of line indexes. -type pair struct{ x, y int } - -// Diff returns an anchored diff of the two texts old and new -// in the “unified diff” format. If old and new are identical, -// Diff returns a nil slice (no output). -// -// Unix diff implementations typically look for a diff with -// the smallest number of lines inserted and removed, -// which can in the worst case take time quadratic in the -// number of lines in the texts. As a result, many implementations -// either can be made to run for a long time or cut off the search -// after a predetermined amount of work. -// -// In contrast, this implementation looks for a diff with the -// smallest number of “unique” lines inserted and removed, -// where unique means a line that appears just once in both old and new. -// We call this an “anchored diff” because the unique lines anchor -// the chosen matching regions. An anchored diff is usually clearer -// than a standard diff, because the algorithm does not try to -// reuse unrelated blank lines or closing braces. -// The algorithm also guarantees to run in O(n log n) time -// instead of the standard O(n²) time. -// -// Some systems call this approach a “patience diff,” named for -// the “patience sorting” algorithm, itself named for a solitaire card game. -// We avoid that name for two reasons. First, the name has been used -// for a few different variants of the algorithm, so it is imprecise. -// Second, the name is frequently interpreted as meaning that you have -// to wait longer (to be patient) for the diff, meaning that it is a slower algorithm, -// when in fact the algorithm is faster than the standard one. -func Diff(oldName string, old []byte, newName string, new []byte) []byte { - if bytes.Equal(old, new) { - return nil - } - x := lines(old) - y := lines(new) - - // Print diff header. - var out bytes.Buffer - fmt.Fprintf(&out, "diff %s %s\n", oldName, newName) - fmt.Fprintf(&out, "--- %s\n", oldName) - fmt.Fprintf(&out, "+++ %s\n", newName) - - // Loop over matches to consider, - // expanding each match to include surrounding lines, - // and then printing diff chunks. - // To avoid setup/teardown cases outside the loop, - // tgs returns a leading {0,0} and trailing {len(x), len(y)} pair - // in the sequence of matches. - var ( - done pair // printed up to x[:done.x] and y[:done.y] - chunk pair // start lines of current chunk - count pair // number of lines from each side in current chunk - ctext []string // lines for current chunk - ) - for _, m := range tgs(x, y) { - if m.x < done.x { - // Already handled scanning forward from earlier match. - continue - } - - // Expand matching lines as far possible, - // establishing that x[start.x:end.x] == y[start.y:end.y]. - // Note that on the first (or last) iteration we may (or definitey do) - // have an empty match: start.x==end.x and start.y==end.y. - start := m - for start.x > done.x && start.y > done.y && x[start.x-1] == y[start.y-1] { - start.x-- - start.y-- - } - end := m - for end.x < len(x) && end.y < len(y) && x[end.x] == y[end.y] { - end.x++ - end.y++ - } - - // Emit the mismatched lines before start into this chunk. - // (No effect on first sentinel iteration, when start = {0,0}.) - for _, s := range x[done.x:start.x] { - ctext = append(ctext, "-"+s) - count.x++ - } - for _, s := range y[done.y:start.y] { - ctext = append(ctext, "+"+s) - count.y++ - } - - // If we're not at EOF and have too few common lines, - // the chunk includes all the common lines and continues. - const C = 3 // number of context lines - if (end.x < len(x) || end.y < len(y)) && - (end.x-start.x < C || (len(ctext) > 0 && end.x-start.x < 2*C)) { - for _, s := range x[start.x:end.x] { - ctext = append(ctext, " "+s) - count.x++ - count.y++ - } - done = end - continue - } - - // End chunk with common lines for context. - if len(ctext) > 0 { - n := end.x - start.x - if n > C { - n = C - } - for _, s := range x[start.x : start.x+n] { - ctext = append(ctext, " "+s) - count.x++ - count.y++ - } - done = pair{start.x + n, start.y + n} - - // Format and emit chunk. - // Convert line numbers to 1-indexed. - // Special case: empty file shows up as 0,0 not 1,0. - if count.x > 0 { - chunk.x++ - } - if count.y > 0 { - chunk.y++ - } - fmt.Fprintf(&out, "@@ -%d,%d +%d,%d @@\n", chunk.x, count.x, chunk.y, count.y) - for _, s := range ctext { - out.WriteString(s) - } - count.x = 0 - count.y = 0 - ctext = ctext[:0] - } - - // If we reached EOF, we're done. - if end.x >= len(x) && end.y >= len(y) { - break - } - - // Otherwise start a new chunk. - chunk = pair{end.x - C, end.y - C} - for _, s := range x[chunk.x:end.x] { - ctext = append(ctext, " "+s) - count.x++ - count.y++ - } - done = end - } - - return out.Bytes() -} - -// lines returns the lines in the file x, including newlines. -// If the file does not end in a newline, one is supplied -// along with a warning about the missing newline. -func lines(x []byte) []string { - l := strings.SplitAfter(string(x), "\n") - if l[len(l)-1] == "" { - l = l[:len(l)-1] - } else { - // Treat last line as having a message about the missing newline attached, - // using the same text as BSD/GNU diff (including the leading backslash). - l[len(l)-1] += "\n\\ No newline at end of file\n" - } - return l -} - -// tgs returns the pairs of indexes of the longest common subsequence -// of unique lines in x and y, where a unique line is one that appears -// once in x and once in y. -// -// The longest common subsequence algorithm is as described in -// Thomas G. Szymanski, “A Special Case of the Maximal Common -// Subsequence Problem,” Princeton TR #170 (January 1975), -// available at https://research.swtch.com/tgs170.pdf. -func tgs(x, y []string) []pair { - // Count the number of times each string appears in a and b. - // We only care about 0, 1, many, counted as 0, -1, -2 - // for the x side and 0, -4, -8 for the y side. - // Using negative numbers now lets us distinguish positive line numbers later. - m := make(map[string]int) - for _, s := range x { - if c := m[s]; c > -2 { - m[s] = c - 1 - } - } - for _, s := range y { - if c := m[s]; c > -8 { - m[s] = c - 4 - } - } - - // Now unique strings can be identified by m[s] = -1+-4. - // - // Gather the indexes of those strings in x and y, building: - // xi[i] = increasing indexes of unique strings in x. - // yi[i] = increasing indexes of unique strings in y. - // inv[i] = index j such that x[xi[i]] = y[yi[j]]. - var xi, yi, inv []int - for i, s := range y { - if m[s] == -1+-4 { - m[s] = len(yi) - yi = append(yi, i) - } - } - for i, s := range x { - if j, ok := m[s]; ok && j >= 0 { - xi = append(xi, i) - inv = append(inv, j) - } - } - - // Apply Algorithm A from Szymanski's paper. - // In those terms, A = J = inv and B = [0, n). - // We add sentinel pairs {0,0}, and {len(x),len(y)} - // to the returned sequence, to help the processing loop. - J := inv - n := len(xi) - T := make([]int, n) - L := make([]int, n) - for i := range T { - T[i] = n + 1 - } - for i := 0; i < n; i++ { - k := sort.Search(n, func(k int) bool { - return T[k] >= J[i] - }) - T[k] = J[i] - L[i] = k + 1 - } - k := 0 - for _, v := range L { - if k < v { - k = v - } - } - seq := make([]pair, 2+k) - seq[1+k] = pair{len(x), len(y)} // sentinel at end - lastj := n - for i := n - 1; i >= 0; i-- { - if L[i] == k && J[i] < lastj { - seq[k] = pair{xi[i], yi[J[i]]} - k-- - } - } - seq[0] = pair{0, 0} // sentinel at start - return seq -} diff --git a/internal/backport/diff/diff_test.go b/internal/backport/diff/diff_test.go deleted file mode 100644 index ee15b0089c..0000000000 --- a/internal/backport/diff/diff_test.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2022 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 diff - -import ( - "bytes" - "path/filepath" - "testing" - - "golang.org/x/tools/txtar" -) - -func clean(text []byte) []byte { - text = bytes.ReplaceAll(text, []byte("$\n"), []byte("\n")) - text = bytes.TrimSuffix(text, []byte("^D\n")) - return text -} - -func Test(t *testing.T) { - files, _ := filepath.Glob("testdata/*.txt") - if len(files) == 0 { - t.Fatalf("no testdata") - } - - for _, file := range files { - t.Run(filepath.Base(file), func(t *testing.T) { - a, err := txtar.ParseFile(file) - if err != nil { - t.Fatal(err) - } - if len(a.Files) != 3 || a.Files[2].Name != "diff" { - t.Fatalf("%s: want three files, third named \"diff\"", file) - } - diffs := Diff(a.Files[0].Name, clean(a.Files[0].Data), a.Files[1].Name, clean(a.Files[1].Data)) - want := clean(a.Files[2].Data) - if !bytes.Equal(diffs, want) { - t.Fatalf("%s: have:\n%s\nwant:\n%s\n%s", file, - diffs, want, Diff("have", diffs, "want", want)) - } - }) - } -} diff --git a/internal/backport/diff/testdata/allnew.txt b/internal/backport/diff/testdata/allnew.txt deleted file mode 100644 index 887564927a..0000000000 --- a/internal/backport/diff/testdata/allnew.txt +++ /dev/null @@ -1,13 +0,0 @@ --- old -- --- new -- -a -b -c --- diff -- -diff old new ---- old -+++ new -@@ -0,0 +1,3 @@ -+a -+b -+c diff --git a/internal/backport/diff/testdata/allold.txt b/internal/backport/diff/testdata/allold.txt deleted file mode 100644 index bcc9ac0ee0..0000000000 --- a/internal/backport/diff/testdata/allold.txt +++ /dev/null @@ -1,13 +0,0 @@ --- old -- -a -b -c --- new -- --- diff -- -diff old new ---- old -+++ new -@@ -1,3 +0,0 @@ --a --b --c diff --git a/internal/backport/diff/testdata/basic.txt b/internal/backport/diff/testdata/basic.txt deleted file mode 100644 index d2565b5d6e..0000000000 --- a/internal/backport/diff/testdata/basic.txt +++ /dev/null @@ -1,35 +0,0 @@ -Example from Hunt and McIlroy, “An Algorithm for Differential File Comparison.” -https://www.cs.dartmouth.edu/~doug/diff.pdf - --- old -- -a -b -c -d -e -f -g --- new -- -w -a -b -x -y -z -e --- diff -- -diff old new ---- old -+++ new -@@ -1,7 +1,7 @@ -+w - a - b --c --d -+x -+y -+z - e --f --g diff --git a/internal/backport/diff/testdata/dups.txt b/internal/backport/diff/testdata/dups.txt deleted file mode 100644 index d10524d0d8..0000000000 --- a/internal/backport/diff/testdata/dups.txt +++ /dev/null @@ -1,40 +0,0 @@ --- old -- -a - -b - -c - -d - -e - -f --- new -- -a - -B - -C - -d - -e - -f --- diff -- -diff old new ---- old -+++ new -@@ -1,8 +1,8 @@ - a - $ --b -- --c -+B -+ -+C - $ - d - $ diff --git a/internal/backport/diff/testdata/end.txt b/internal/backport/diff/testdata/end.txt deleted file mode 100644 index 158637c135..0000000000 --- a/internal/backport/diff/testdata/end.txt +++ /dev/null @@ -1,38 +0,0 @@ --- old -- -1 -2 -3 -4 -5 -6 -7 -eight -nine -ten -eleven --- new -- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 --- diff -- -diff old new ---- old -+++ new -@@ -5,7 +5,6 @@ - 5 - 6 - 7 --eight --nine --ten --eleven -+8 -+9 -+10 diff --git a/internal/backport/diff/testdata/eof.txt b/internal/backport/diff/testdata/eof.txt deleted file mode 100644 index 5dc145c4de..0000000000 --- a/internal/backport/diff/testdata/eof.txt +++ /dev/null @@ -1,9 +0,0 @@ --- old -- -a -b -c^D --- new -- -a -b -c^D --- diff -- diff --git a/internal/backport/diff/testdata/eof1.txt b/internal/backport/diff/testdata/eof1.txt deleted file mode 100644 index 1ebf621e92..0000000000 --- a/internal/backport/diff/testdata/eof1.txt +++ /dev/null @@ -1,18 +0,0 @@ --- old -- -a -b -c --- new -- -a -b -c^D --- diff -- -diff old new ---- old -+++ new -@@ -1,3 +1,3 @@ - a - b --c -+c -\ No newline at end of file diff --git a/internal/backport/diff/testdata/eof2.txt b/internal/backport/diff/testdata/eof2.txt deleted file mode 100644 index 047705e686..0000000000 --- a/internal/backport/diff/testdata/eof2.txt +++ /dev/null @@ -1,18 +0,0 @@ --- old -- -a -b -c^D --- new -- -a -b -c --- diff -- -diff old new ---- old -+++ new -@@ -1,3 +1,3 @@ - a - b --c -\ No newline at end of file -+c diff --git a/internal/backport/diff/testdata/long.txt b/internal/backport/diff/testdata/long.txt deleted file mode 100644 index 3fc99f71d5..0000000000 --- a/internal/backport/diff/testdata/long.txt +++ /dev/null @@ -1,62 +0,0 @@ --- old -- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -14½ -15 -16 -17 -18 -19 -20 --- new -- -1 -2 -3 -4 -5 -6 -8 -9 -10 -11 -12 -13 -14 -17 -18 -19 -20 --- diff -- -diff old new ---- old -+++ new -@@ -4,7 +4,6 @@ - 4 - 5 - 6 --7 - 8 - 9 - 10 -@@ -12,9 +11,6 @@ - 12 - 13 - 14 --14½ --15 --16 - 17 - 18 - 19 diff --git a/internal/backport/diff/testdata/same.txt b/internal/backport/diff/testdata/same.txt deleted file mode 100644 index 86b1100d81..0000000000 --- a/internal/backport/diff/testdata/same.txt +++ /dev/null @@ -1,5 +0,0 @@ --- old -- -hello world --- new -- -hello world --- diff -- diff --git a/internal/backport/diff/testdata/start.txt b/internal/backport/diff/testdata/start.txt deleted file mode 100644 index 217b2fdc9f..0000000000 --- a/internal/backport/diff/testdata/start.txt +++ /dev/null @@ -1,34 +0,0 @@ --- old -- -e -pi -4 -5 -6 -7 -8 -9 -10 --- new -- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 --- diff -- -diff old new ---- old -+++ new -@@ -1,5 +1,6 @@ --e --pi -+1 -+2 -+3 - 4 - 5 - 6 diff --git a/internal/backport/diff/testdata/triv.txt b/internal/backport/diff/testdata/triv.txt deleted file mode 100644 index ab5759fcb2..0000000000 --- a/internal/backport/diff/testdata/triv.txt +++ /dev/null @@ -1,40 +0,0 @@ -Another example from Hunt and McIlroy, -“An Algorithm for Differential File Comparison.” -https://www.cs.dartmouth.edu/~doug/diff.pdf - -Anchored diff gives up on finding anything, -since there are no unique lines. - --- old -- -a -b -c -a -b -b -a --- new -- -c -a -b -a -b -c --- diff -- -diff old new ---- old -+++ new -@@ -1,7 +1,6 @@ --a --b --c --a --b --b --a -+c -+a -+b -+a -+b -+c diff --git a/internal/backport/fmtsort/sort.go b/internal/backport/fmtsort/sort.go deleted file mode 100644 index e0585dc298..0000000000 --- a/internal/backport/fmtsort/sort.go +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright 2018 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 fmtsort provides a general stable ordering mechanism -// for maps, on behalf of the fmt and text/template packages. -// It is not guaranteed to be efficient and works only for types -// that are valid map keys. -package fmtsort - -import ( - "reflect" - "sort" -) - -// Note: Throughout this package we avoid calling reflect.Value.Interface as -// it is not always legal to do so and it's easier to avoid the issue than to face it. - -// SortedMap represents a map's keys and values. The keys and values are -// aligned in index order: Value[i] is the value in the map corresponding to Key[i]. -type SortedMap struct { - Key []reflect.Value - Value []reflect.Value -} - -func (o *SortedMap) Len() int { return len(o.Key) } -func (o *SortedMap) Less(i, j int) bool { return compare(o.Key[i], o.Key[j]) < 0 } -func (o *SortedMap) Swap(i, j int) { - o.Key[i], o.Key[j] = o.Key[j], o.Key[i] - o.Value[i], o.Value[j] = o.Value[j], o.Value[i] -} - -// Sort accepts a map and returns a SortedMap that has the same keys and -// values but in a stable sorted order according to the keys, modulo issues -// raised by unorderable key values such as NaNs. -// -// The ordering rules are more general than with Go's < operator: -// -// - when applicable, nil compares low -// - ints, floats, and strings order by < -// - NaN compares less than non-NaN floats -// - bool compares false before true -// - complex compares real, then imag -// - pointers compare by machine address -// - channel values compare by machine address -// - structs compare each field in turn -// - arrays compare each element in turn. -// Otherwise identical arrays compare by length. -// - interface values compare first by reflect.Type describing the concrete type -// and then by concrete value as described in the previous rules. -func Sort(mapValue reflect.Value) *SortedMap { - if mapValue.Type().Kind() != reflect.Map { - return nil - } - // Note: this code is arranged to not panic even in the presence - // of a concurrent map update. The runtime is responsible for - // yelling loudly if that happens. See issue 33275. - n := mapValue.Len() - key := make([]reflect.Value, 0, n) - value := make([]reflect.Value, 0, n) - iter := mapValue.MapRange() - for iter.Next() { - key = append(key, iter.Key()) - value = append(value, iter.Value()) - } - sorted := &SortedMap{ - Key: key, - Value: value, - } - sort.Stable(sorted) - return sorted -} - -// compare compares two values of the same type. It returns -1, 0, 1 -// according to whether a > b (1), a == b (0), or a < b (-1). -// If the types differ, it returns -1. -// See the comment on Sort for the comparison rules. -func compare(aVal, bVal reflect.Value) int { - aType, bType := aVal.Type(), bVal.Type() - if aType != bType { - return -1 // No good answer possible, but don't return 0: they're not equal. - } - switch aVal.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - a, b := aVal.Int(), bVal.Int() - switch { - case a < b: - return -1 - case a > b: - return 1 - default: - return 0 - } - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - a, b := aVal.Uint(), bVal.Uint() - switch { - case a < b: - return -1 - case a > b: - return 1 - default: - return 0 - } - case reflect.String: - a, b := aVal.String(), bVal.String() - switch { - case a < b: - return -1 - case a > b: - return 1 - default: - return 0 - } - case reflect.Float32, reflect.Float64: - return floatCompare(aVal.Float(), bVal.Float()) - case reflect.Complex64, reflect.Complex128: - a, b := aVal.Complex(), bVal.Complex() - if c := floatCompare(real(a), real(b)); c != 0 { - return c - } - return floatCompare(imag(a), imag(b)) - case reflect.Bool: - a, b := aVal.Bool(), bVal.Bool() - switch { - case a == b: - return 0 - case a: - return 1 - default: - return -1 - } - case reflect.Ptr, reflect.UnsafePointer: - a, b := aVal.Pointer(), bVal.Pointer() - switch { - case a < b: - return -1 - case a > b: - return 1 - default: - return 0 - } - case reflect.Chan: - if c, ok := nilCompare(aVal, bVal); ok { - return c - } - ap, bp := aVal.Pointer(), bVal.Pointer() - switch { - case ap < bp: - return -1 - case ap > bp: - return 1 - default: - return 0 - } - case reflect.Struct: - for i := 0; i < aVal.NumField(); i++ { - if c := compare(aVal.Field(i), bVal.Field(i)); c != 0 { - return c - } - } - return 0 - case reflect.Array: - for i := 0; i < aVal.Len(); i++ { - if c := compare(aVal.Index(i), bVal.Index(i)); c != 0 { - return c - } - } - return 0 - case reflect.Interface: - if c, ok := nilCompare(aVal, bVal); ok { - return c - } - c := compare(reflect.ValueOf(aVal.Elem().Type()), reflect.ValueOf(bVal.Elem().Type())) - if c != 0 { - return c - } - return compare(aVal.Elem(), bVal.Elem()) - default: - // Certain types cannot appear as keys (maps, funcs, slices), but be explicit. - panic("bad type in compare: " + aType.String()) - } -} - -// nilCompare checks whether either value is nil. If not, the boolean is false. -// If either value is nil, the boolean is true and the integer is the comparison -// value. The comparison is defined to be 0 if both are nil, otherwise the one -// nil value compares low. Both arguments must represent a chan, func, -// interface, map, pointer, or slice. -func nilCompare(aVal, bVal reflect.Value) (int, bool) { - if aVal.IsNil() { - if bVal.IsNil() { - return 0, true - } - return -1, true - } - if bVal.IsNil() { - return 1, true - } - return 0, false -} - -// floatCompare compares two floating-point values. NaNs compare low. -func floatCompare(a, b float64) int { - switch { - case isNaN(a): - return -1 // No good answer if b is a NaN so don't bother checking. - case isNaN(b): - return 1 - case a < b: - return -1 - case a > b: - return 1 - } - return 0 -} - -func isNaN(a float64) bool { - return a != a -} diff --git a/internal/backport/go/ast/ast.go b/internal/backport/go/ast/ast.go deleted file mode 100644 index 9efdcf7c51..0000000000 --- a/internal/backport/go/ast/ast.go +++ /dev/null @@ -1,1067 +0,0 @@ -// Copyright 2009 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 ast declares the types used to represent syntax trees for Go -// packages. -package ast - -import ( - "strings" - - "golang.org/x/website/internal/backport/go/token" -) - -// ---------------------------------------------------------------------------- -// Interfaces -// -// There are 3 main classes of nodes: Expressions and type nodes, -// statement nodes, and declaration nodes. The node names usually -// match the corresponding Go spec production names to which they -// correspond. The node fields correspond to the individual parts -// of the respective productions. -// -// All nodes contain position information marking the beginning of -// the corresponding source text segment; it is accessible via the -// Pos accessor method. Nodes may contain additional position info -// for language constructs where comments may be found between parts -// of the construct (typically any larger, parenthesized subpart). -// That position information is needed to properly position comments -// when printing the construct. - -// All node types implement the Node interface. -type Node interface { - Pos() token.Pos // position of first character belonging to the node - End() token.Pos // position of first character immediately after the node -} - -// All expression nodes implement the Expr interface. -type Expr interface { - Node - exprNode() -} - -// All statement nodes implement the Stmt interface. -type Stmt interface { - Node - stmtNode() -} - -// All declaration nodes implement the Decl interface. -type Decl interface { - Node - declNode() -} - -// ---------------------------------------------------------------------------- -// Comments - -// A Comment node represents a single //-style or /*-style comment. -// -// The Text field contains the comment text without carriage returns (\r) that -// may have been present in the source. Because a comment's end position is -// computed using len(Text), the position reported by End() does not match the -// true source end position for comments containing carriage returns. -type Comment struct { - Slash token.Pos // position of "/" starting the comment - Text string // comment text (excluding '\n' for //-style comments) -} - -func (c *Comment) Pos() token.Pos { return c.Slash } -func (c *Comment) End() token.Pos { return token.Pos(int(c.Slash) + len(c.Text)) } - -// A CommentGroup represents a sequence of comments -// with no other tokens and no empty lines between. -type CommentGroup struct { - List []*Comment // len(List) > 0 -} - -func (g *CommentGroup) Pos() token.Pos { return g.List[0].Pos() } -func (g *CommentGroup) End() token.Pos { return g.List[len(g.List)-1].End() } - -func isWhitespace(ch byte) bool { return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' } - -func stripTrailingWhitespace(s string) string { - i := len(s) - for i > 0 && isWhitespace(s[i-1]) { - i-- - } - return s[0:i] -} - -// Text returns the text of the comment. -// Comment markers (//, /*, and */), the first space of a line comment, and -// leading and trailing empty lines are removed. -// Comment directives like "//line" and "//go:noinline" are also removed. -// Multiple empty lines are reduced to one, and trailing space on lines is trimmed. -// Unless the result is empty, it is newline-terminated. -func (g *CommentGroup) Text() string { - if g == nil { - return "" - } - comments := make([]string, len(g.List)) - for i, c := range g.List { - comments[i] = c.Text - } - - lines := make([]string, 0, 10) // most comments are less than 10 lines - for _, c := range comments { - // Remove comment markers. - // The parser has given us exactly the comment text. - switch c[1] { - case '/': - //-style comment (no newline at the end) - c = c[2:] - if len(c) == 0 { - // empty line - break - } - if c[0] == ' ' { - // strip first space - required for Example tests - c = c[1:] - break - } - if isDirective(c) { - // Ignore //go:noinline, //line, and so on. - continue - } - case '*': - /*-style comment */ - c = c[2 : len(c)-2] - } - - // Split on newlines. - cl := strings.Split(c, "\n") - - // Walk lines, stripping trailing white space and adding to list. - for _, l := range cl { - lines = append(lines, stripTrailingWhitespace(l)) - } - } - - // Remove leading blank lines; convert runs of - // interior blank lines to a single blank line. - n := 0 - for _, line := range lines { - if line != "" || n > 0 && lines[n-1] != "" { - lines[n] = line - n++ - } - } - lines = lines[0:n] - - // Add final "" entry to get trailing newline from Join. - if n > 0 && lines[n-1] != "" { - lines = append(lines, "") - } - - return strings.Join(lines, "\n") -} - -// isDirective reports whether c is a comment directive. -// This code is also in go/printer. -func isDirective(c string) bool { - // "//line " is a line directive. - // "//extern " is for gccgo. - // "//export " is for cgo. - // (The // has been removed.) - if strings.HasPrefix(c, "line ") || strings.HasPrefix(c, "extern ") || strings.HasPrefix(c, "export ") { - return true - } - - // "//[a-z0-9]+:[a-z0-9]" - // (The // has been removed.) - colon := strings.Index(c, ":") - if colon <= 0 || colon+1 >= len(c) { - return false - } - for i := 0; i <= colon+1; i++ { - if i == colon { - continue - } - b := c[i] - if !('a' <= b && b <= 'z' || '0' <= b && b <= '9') { - return false - } - } - return true -} - -// ---------------------------------------------------------------------------- -// Expressions and types - -// A Field represents a Field declaration list in a struct type, -// a method list in an interface type, or a parameter/result declaration -// in a signature. -// Field.Names is nil for unnamed parameters (parameter lists which only contain types) -// and embedded struct fields. In the latter case, the field name is the type name. -type Field struct { - Doc *CommentGroup // associated documentation; or nil - Names []*Ident // field/method/(type) parameter names; or nil - Type Expr // field/method/parameter type; or nil - Tag *BasicLit // field tag; or nil - Comment *CommentGroup // line comments; or nil -} - -func (f *Field) Pos() token.Pos { - if len(f.Names) > 0 { - return f.Names[0].Pos() - } - if f.Type != nil { - return f.Type.Pos() - } - return token.NoPos -} - -func (f *Field) End() token.Pos { - if f.Tag != nil { - return f.Tag.End() - } - if f.Type != nil { - return f.Type.End() - } - if len(f.Names) > 0 { - return f.Names[len(f.Names)-1].End() - } - return token.NoPos -} - -// A FieldList represents a list of Fields, enclosed by parentheses, -// curly braces, or square brackets. -type FieldList struct { - Opening token.Pos // position of opening parenthesis/brace/bracket, if any - List []*Field // field list; or nil - Closing token.Pos // position of closing parenthesis/brace/bracket, if any -} - -func (f *FieldList) Pos() token.Pos { - if f.Opening.IsValid() { - return f.Opening - } - // the list should not be empty in this case; - // be conservative and guard against bad ASTs - if len(f.List) > 0 { - return f.List[0].Pos() - } - return token.NoPos -} - -func (f *FieldList) End() token.Pos { - if f.Closing.IsValid() { - return f.Closing + 1 - } - // the list should not be empty in this case; - // be conservative and guard against bad ASTs - if n := len(f.List); n > 0 { - return f.List[n-1].End() - } - return token.NoPos -} - -// NumFields returns the number of parameters or struct fields represented by a FieldList. -func (f *FieldList) NumFields() int { - n := 0 - if f != nil { - for _, g := range f.List { - m := len(g.Names) - if m == 0 { - m = 1 - } - n += m - } - } - return n -} - -// An expression is represented by a tree consisting of one -// or more of the following concrete expression nodes. -type ( - // A BadExpr node is a placeholder for an expression containing - // syntax errors for which a correct expression node cannot be - // created. - // - BadExpr struct { - From, To token.Pos // position range of bad expression - } - - // An Ident node represents an identifier. - Ident struct { - NamePos token.Pos // identifier position - Name string // identifier name - Obj *Object // denoted object; or nil - } - - // An Ellipsis node stands for the "..." type in a - // parameter list or the "..." length in an array type. - // - Ellipsis struct { - Ellipsis token.Pos // position of "..." - Elt Expr // ellipsis element type (parameter lists only); or nil - } - - // A BasicLit node represents a literal of basic type. - BasicLit struct { - ValuePos token.Pos // literal position - Kind token.Token // token.INT, token.FLOAT, token.IMAG, token.CHAR, or token.STRING - Value string // literal string; e.g. 42, 0x7f, 3.14, 1e-9, 2.4i, 'a', '\x7f', "foo" or `\m\n\o` - } - - // A FuncLit node represents a function literal. - FuncLit struct { - Type *FuncType // function type - Body *BlockStmt // function body - } - - // A CompositeLit node represents a composite literal. - CompositeLit struct { - Type Expr // literal type; or nil - Lbrace token.Pos // position of "{" - Elts []Expr // list of composite elements; or nil - Rbrace token.Pos // position of "}" - Incomplete bool // true if (source) expressions are missing in the Elts list - } - - // A ParenExpr node represents a parenthesized expression. - ParenExpr struct { - Lparen token.Pos // position of "(" - X Expr // parenthesized expression - Rparen token.Pos // position of ")" - } - - // A SelectorExpr node represents an expression followed by a selector. - SelectorExpr struct { - X Expr // expression - Sel *Ident // field selector - } - - // An IndexExpr node represents an expression followed by an index. - IndexExpr struct { - X Expr // expression - Lbrack token.Pos // position of "[" - Index Expr // index expression - Rbrack token.Pos // position of "]" - } - - // An IndexListExpr node represents an expression followed by multiple - // indices. - IndexListExpr struct { - X Expr // expression - Lbrack token.Pos // position of "[" - Indices []Expr // index expressions - Rbrack token.Pos // position of "]" - } - - // A SliceExpr node represents an expression followed by slice indices. - SliceExpr struct { - X Expr // expression - Lbrack token.Pos // position of "[" - Low Expr // begin of slice range; or nil - High Expr // end of slice range; or nil - Max Expr // maximum capacity of slice; or nil - Slice3 bool // true if 3-index slice (2 colons present) - Rbrack token.Pos // position of "]" - } - - // A TypeAssertExpr node represents an expression followed by a - // type assertion. - // - TypeAssertExpr struct { - X Expr // expression - Lparen token.Pos // position of "(" - Type Expr // asserted type; nil means type switch X.(type) - Rparen token.Pos // position of ")" - } - - // A CallExpr node represents an expression followed by an argument list. - CallExpr struct { - Fun Expr // function expression - Lparen token.Pos // position of "(" - Args []Expr // function arguments; or nil - Ellipsis token.Pos // position of "..." (token.NoPos if there is no "...") - Rparen token.Pos // position of ")" - } - - // A StarExpr node represents an expression of the form "*" Expression. - // Semantically it could be a unary "*" expression, or a pointer type. - // - StarExpr struct { - Star token.Pos // position of "*" - X Expr // operand - } - - // A UnaryExpr node represents a unary expression. - // Unary "*" expressions are represented via StarExpr nodes. - // - UnaryExpr struct { - OpPos token.Pos // position of Op - Op token.Token // operator - X Expr // operand - } - - // A BinaryExpr node represents a binary expression. - BinaryExpr struct { - X Expr // left operand - OpPos token.Pos // position of Op - Op token.Token // operator - Y Expr // right operand - } - - // A KeyValueExpr node represents (key : value) pairs - // in composite literals. - // - KeyValueExpr struct { - Key Expr - Colon token.Pos // position of ":" - Value Expr - } -) - -// The direction of a channel type is indicated by a bit -// mask including one or both of the following constants. -type ChanDir int - -const ( - SEND ChanDir = 1 << iota - RECV -) - -// A type is represented by a tree consisting of one -// or more of the following type-specific expression -// nodes. -type ( - // An ArrayType node represents an array or slice type. - ArrayType struct { - Lbrack token.Pos // position of "[" - Len Expr // Ellipsis node for [...]T array types, nil for slice types - Elt Expr // element type - } - - // A StructType node represents a struct type. - StructType struct { - Struct token.Pos // position of "struct" keyword - Fields *FieldList // list of field declarations - Incomplete bool // true if (source) fields are missing in the Fields list - } - - // Pointer types are represented via StarExpr nodes. - - // A FuncType node represents a function type. - FuncType struct { - Func token.Pos // position of "func" keyword (token.NoPos if there is no "func") - TypeParams *FieldList // type parameters; or nil - Params *FieldList // (incoming) parameters; non-nil - Results *FieldList // (outgoing) results; or nil - } - - // An InterfaceType node represents an interface type. - InterfaceType struct { - Interface token.Pos // position of "interface" keyword - Methods *FieldList // list of embedded interfaces, methods, or types - Incomplete bool // true if (source) methods or types are missing in the Methods list - } - - // A MapType node represents a map type. - MapType struct { - Map token.Pos // position of "map" keyword - Key Expr - Value Expr - } - - // A ChanType node represents a channel type. - ChanType struct { - Begin token.Pos // position of "chan" keyword or "<-" (whichever comes first) - Arrow token.Pos // position of "<-" (token.NoPos if there is no "<-") - Dir ChanDir // channel direction - Value Expr // value type - } -) - -// Pos and End implementations for expression/type nodes. - -func (x *BadExpr) Pos() token.Pos { return x.From } -func (x *Ident) Pos() token.Pos { return x.NamePos } -func (x *Ellipsis) Pos() token.Pos { return x.Ellipsis } -func (x *BasicLit) Pos() token.Pos { return x.ValuePos } -func (x *FuncLit) Pos() token.Pos { return x.Type.Pos() } -func (x *CompositeLit) Pos() token.Pos { - if x.Type != nil { - return x.Type.Pos() - } - return x.Lbrace -} -func (x *ParenExpr) Pos() token.Pos { return x.Lparen } -func (x *SelectorExpr) Pos() token.Pos { return x.X.Pos() } -func (x *IndexExpr) Pos() token.Pos { return x.X.Pos() } -func (x *IndexListExpr) Pos() token.Pos { return x.X.Pos() } -func (x *SliceExpr) Pos() token.Pos { return x.X.Pos() } -func (x *TypeAssertExpr) Pos() token.Pos { return x.X.Pos() } -func (x *CallExpr) Pos() token.Pos { return x.Fun.Pos() } -func (x *StarExpr) Pos() token.Pos { return x.Star } -func (x *UnaryExpr) Pos() token.Pos { return x.OpPos } -func (x *BinaryExpr) Pos() token.Pos { return x.X.Pos() } -func (x *KeyValueExpr) Pos() token.Pos { return x.Key.Pos() } -func (x *ArrayType) Pos() token.Pos { return x.Lbrack } -func (x *StructType) Pos() token.Pos { return x.Struct } -func (x *FuncType) Pos() token.Pos { - if x.Func.IsValid() || x.Params == nil { // see issue 3870 - return x.Func - } - return x.Params.Pos() // interface method declarations have no "func" keyword -} -func (x *InterfaceType) Pos() token.Pos { return x.Interface } -func (x *MapType) Pos() token.Pos { return x.Map } -func (x *ChanType) Pos() token.Pos { return x.Begin } - -func (x *BadExpr) End() token.Pos { return x.To } -func (x *Ident) End() token.Pos { return token.Pos(int(x.NamePos) + len(x.Name)) } -func (x *Ellipsis) End() token.Pos { - if x.Elt != nil { - return x.Elt.End() - } - return x.Ellipsis + 3 // len("...") -} -func (x *BasicLit) End() token.Pos { return token.Pos(int(x.ValuePos) + len(x.Value)) } -func (x *FuncLit) End() token.Pos { return x.Body.End() } -func (x *CompositeLit) End() token.Pos { return x.Rbrace + 1 } -func (x *ParenExpr) End() token.Pos { return x.Rparen + 1 } -func (x *SelectorExpr) End() token.Pos { return x.Sel.End() } -func (x *IndexExpr) End() token.Pos { return x.Rbrack + 1 } -func (x *IndexListExpr) End() token.Pos { return x.Rbrack + 1 } -func (x *SliceExpr) End() token.Pos { return x.Rbrack + 1 } -func (x *TypeAssertExpr) End() token.Pos { return x.Rparen + 1 } -func (x *CallExpr) End() token.Pos { return x.Rparen + 1 } -func (x *StarExpr) End() token.Pos { return x.X.End() } -func (x *UnaryExpr) End() token.Pos { return x.X.End() } -func (x *BinaryExpr) End() token.Pos { return x.Y.End() } -func (x *KeyValueExpr) End() token.Pos { return x.Value.End() } -func (x *ArrayType) End() token.Pos { return x.Elt.End() } -func (x *StructType) End() token.Pos { return x.Fields.End() } -func (x *FuncType) End() token.Pos { - if x.Results != nil { - return x.Results.End() - } - return x.Params.End() -} -func (x *InterfaceType) End() token.Pos { return x.Methods.End() } -func (x *MapType) End() token.Pos { return x.Value.End() } -func (x *ChanType) End() token.Pos { return x.Value.End() } - -// exprNode() ensures that only expression/type nodes can be -// assigned to an Expr. -func (*BadExpr) exprNode() {} -func (*Ident) exprNode() {} -func (*Ellipsis) exprNode() {} -func (*BasicLit) exprNode() {} -func (*FuncLit) exprNode() {} -func (*CompositeLit) exprNode() {} -func (*ParenExpr) exprNode() {} -func (*SelectorExpr) exprNode() {} -func (*IndexExpr) exprNode() {} -func (*IndexListExpr) exprNode() {} -func (*SliceExpr) exprNode() {} -func (*TypeAssertExpr) exprNode() {} -func (*CallExpr) exprNode() {} -func (*StarExpr) exprNode() {} -func (*UnaryExpr) exprNode() {} -func (*BinaryExpr) exprNode() {} -func (*KeyValueExpr) exprNode() {} - -func (*ArrayType) exprNode() {} -func (*StructType) exprNode() {} -func (*FuncType) exprNode() {} -func (*InterfaceType) exprNode() {} -func (*MapType) exprNode() {} -func (*ChanType) exprNode() {} - -// ---------------------------------------------------------------------------- -// Convenience functions for Idents - -// NewIdent creates a new Ident without position. -// Useful for ASTs generated by code other than the Go parser. -func NewIdent(name string) *Ident { return &Ident{token.NoPos, name, nil} } - -// IsExported reports whether name starts with an upper-case letter. -func IsExported(name string) bool { return token.IsExported(name) } - -// IsExported reports whether id starts with an upper-case letter. -func (id *Ident) IsExported() bool { return token.IsExported(id.Name) } - -func (id *Ident) String() string { - if id != nil { - return id.Name - } - return "" -} - -// ---------------------------------------------------------------------------- -// Statements - -// A statement is represented by a tree consisting of one -// or more of the following concrete statement nodes. -type ( - // A BadStmt node is a placeholder for statements containing - // syntax errors for which no correct statement nodes can be - // created. - // - BadStmt struct { - From, To token.Pos // position range of bad statement - } - - // A DeclStmt node represents a declaration in a statement list. - DeclStmt struct { - Decl Decl // *GenDecl with CONST, TYPE, or VAR token - } - - // An EmptyStmt node represents an empty statement. - // The "position" of the empty statement is the position - // of the immediately following (explicit or implicit) semicolon. - // - EmptyStmt struct { - Semicolon token.Pos // position of following ";" - Implicit bool // if set, ";" was omitted in the source - } - - // A LabeledStmt node represents a labeled statement. - LabeledStmt struct { - Label *Ident - Colon token.Pos // position of ":" - Stmt Stmt - } - - // An ExprStmt node represents a (stand-alone) expression - // in a statement list. - // - ExprStmt struct { - X Expr // expression - } - - // A SendStmt node represents a send statement. - SendStmt struct { - Chan Expr - Arrow token.Pos // position of "<-" - Value Expr - } - - // An IncDecStmt node represents an increment or decrement statement. - IncDecStmt struct { - X Expr - TokPos token.Pos // position of Tok - Tok token.Token // INC or DEC - } - - // An AssignStmt node represents an assignment or - // a short variable declaration. - // - AssignStmt struct { - Lhs []Expr - TokPos token.Pos // position of Tok - Tok token.Token // assignment token, DEFINE - Rhs []Expr - } - - // A GoStmt node represents a go statement. - GoStmt struct { - Go token.Pos // position of "go" keyword - Call *CallExpr - } - - // A DeferStmt node represents a defer statement. - DeferStmt struct { - Defer token.Pos // position of "defer" keyword - Call *CallExpr - } - - // A ReturnStmt node represents a return statement. - ReturnStmt struct { - Return token.Pos // position of "return" keyword - Results []Expr // result expressions; or nil - } - - // A BranchStmt node represents a break, continue, goto, - // or fallthrough statement. - // - BranchStmt struct { - TokPos token.Pos // position of Tok - Tok token.Token // keyword token (BREAK, CONTINUE, GOTO, FALLTHROUGH) - Label *Ident // label name; or nil - } - - // A BlockStmt node represents a braced statement list. - BlockStmt struct { - Lbrace token.Pos // position of "{" - List []Stmt - Rbrace token.Pos // position of "}", if any (may be absent due to syntax error) - } - - // An IfStmt node represents an if statement. - IfStmt struct { - If token.Pos // position of "if" keyword - Init Stmt // initialization statement; or nil - Cond Expr // condition - Body *BlockStmt - Else Stmt // else branch; or nil - } - - // A CaseClause represents a case of an expression or type switch statement. - CaseClause struct { - Case token.Pos // position of "case" or "default" keyword - List []Expr // list of expressions or types; nil means default case - Colon token.Pos // position of ":" - Body []Stmt // statement list; or nil - } - - // A SwitchStmt node represents an expression switch statement. - SwitchStmt struct { - Switch token.Pos // position of "switch" keyword - Init Stmt // initialization statement; or nil - Tag Expr // tag expression; or nil - Body *BlockStmt // CaseClauses only - } - - // A TypeSwitchStmt node represents a type switch statement. - TypeSwitchStmt struct { - Switch token.Pos // position of "switch" keyword - Init Stmt // initialization statement; or nil - Assign Stmt // x := y.(type) or y.(type) - Body *BlockStmt // CaseClauses only - } - - // A CommClause node represents a case of a select statement. - CommClause struct { - Case token.Pos // position of "case" or "default" keyword - Comm Stmt // send or receive statement; nil means default case - Colon token.Pos // position of ":" - Body []Stmt // statement list; or nil - } - - // A SelectStmt node represents a select statement. - SelectStmt struct { - Select token.Pos // position of "select" keyword - Body *BlockStmt // CommClauses only - } - - // A ForStmt represents a for statement. - ForStmt struct { - For token.Pos // position of "for" keyword - Init Stmt // initialization statement; or nil - Cond Expr // condition; or nil - Post Stmt // post iteration statement; or nil - Body *BlockStmt - } - - // A RangeStmt represents a for statement with a range clause. - RangeStmt struct { - For token.Pos // position of "for" keyword - Key, Value Expr // Key, Value may be nil - TokPos token.Pos // position of Tok; invalid if Key == nil - Tok token.Token // ILLEGAL if Key == nil, ASSIGN, DEFINE - X Expr // value to range over - Body *BlockStmt - } -) - -// Pos and End implementations for statement nodes. - -func (s *BadStmt) Pos() token.Pos { return s.From } -func (s *DeclStmt) Pos() token.Pos { return s.Decl.Pos() } -func (s *EmptyStmt) Pos() token.Pos { return s.Semicolon } -func (s *LabeledStmt) Pos() token.Pos { return s.Label.Pos() } -func (s *ExprStmt) Pos() token.Pos { return s.X.Pos() } -func (s *SendStmt) Pos() token.Pos { return s.Chan.Pos() } -func (s *IncDecStmt) Pos() token.Pos { return s.X.Pos() } -func (s *AssignStmt) Pos() token.Pos { return s.Lhs[0].Pos() } -func (s *GoStmt) Pos() token.Pos { return s.Go } -func (s *DeferStmt) Pos() token.Pos { return s.Defer } -func (s *ReturnStmt) Pos() token.Pos { return s.Return } -func (s *BranchStmt) Pos() token.Pos { return s.TokPos } -func (s *BlockStmt) Pos() token.Pos { return s.Lbrace } -func (s *IfStmt) Pos() token.Pos { return s.If } -func (s *CaseClause) Pos() token.Pos { return s.Case } -func (s *SwitchStmt) Pos() token.Pos { return s.Switch } -func (s *TypeSwitchStmt) Pos() token.Pos { return s.Switch } -func (s *CommClause) Pos() token.Pos { return s.Case } -func (s *SelectStmt) Pos() token.Pos { return s.Select } -func (s *ForStmt) Pos() token.Pos { return s.For } -func (s *RangeStmt) Pos() token.Pos { return s.For } - -func (s *BadStmt) End() token.Pos { return s.To } -func (s *DeclStmt) End() token.Pos { return s.Decl.End() } -func (s *EmptyStmt) End() token.Pos { - if s.Implicit { - return s.Semicolon - } - return s.Semicolon + 1 /* len(";") */ -} -func (s *LabeledStmt) End() token.Pos { return s.Stmt.End() } -func (s *ExprStmt) End() token.Pos { return s.X.End() } -func (s *SendStmt) End() token.Pos { return s.Value.End() } -func (s *IncDecStmt) End() token.Pos { - return s.TokPos + 2 /* len("++") */ -} -func (s *AssignStmt) End() token.Pos { return s.Rhs[len(s.Rhs)-1].End() } -func (s *GoStmt) End() token.Pos { return s.Call.End() } -func (s *DeferStmt) End() token.Pos { return s.Call.End() } -func (s *ReturnStmt) End() token.Pos { - if n := len(s.Results); n > 0 { - return s.Results[n-1].End() - } - return s.Return + 6 // len("return") -} -func (s *BranchStmt) End() token.Pos { - if s.Label != nil { - return s.Label.End() - } - return token.Pos(int(s.TokPos) + len(s.Tok.String())) -} -func (s *BlockStmt) End() token.Pos { - if s.Rbrace.IsValid() { - return s.Rbrace + 1 - } - if n := len(s.List); n > 0 { - return s.List[n-1].End() - } - return s.Lbrace + 1 -} -func (s *IfStmt) End() token.Pos { - if s.Else != nil { - return s.Else.End() - } - return s.Body.End() -} -func (s *CaseClause) End() token.Pos { - if n := len(s.Body); n > 0 { - return s.Body[n-1].End() - } - return s.Colon + 1 -} -func (s *SwitchStmt) End() token.Pos { return s.Body.End() } -func (s *TypeSwitchStmt) End() token.Pos { return s.Body.End() } -func (s *CommClause) End() token.Pos { - if n := len(s.Body); n > 0 { - return s.Body[n-1].End() - } - return s.Colon + 1 -} -func (s *SelectStmt) End() token.Pos { return s.Body.End() } -func (s *ForStmt) End() token.Pos { return s.Body.End() } -func (s *RangeStmt) End() token.Pos { return s.Body.End() } - -// stmtNode() ensures that only statement nodes can be -// assigned to a Stmt. -func (*BadStmt) stmtNode() {} -func (*DeclStmt) stmtNode() {} -func (*EmptyStmt) stmtNode() {} -func (*LabeledStmt) stmtNode() {} -func (*ExprStmt) stmtNode() {} -func (*SendStmt) stmtNode() {} -func (*IncDecStmt) stmtNode() {} -func (*AssignStmt) stmtNode() {} -func (*GoStmt) stmtNode() {} -func (*DeferStmt) stmtNode() {} -func (*ReturnStmt) stmtNode() {} -func (*BranchStmt) stmtNode() {} -func (*BlockStmt) stmtNode() {} -func (*IfStmt) stmtNode() {} -func (*CaseClause) stmtNode() {} -func (*SwitchStmt) stmtNode() {} -func (*TypeSwitchStmt) stmtNode() {} -func (*CommClause) stmtNode() {} -func (*SelectStmt) stmtNode() {} -func (*ForStmt) stmtNode() {} -func (*RangeStmt) stmtNode() {} - -// ---------------------------------------------------------------------------- -// Declarations - -// A Spec node represents a single (non-parenthesized) import, -// constant, type, or variable declaration. -type ( - // The Spec type stands for any of *ImportSpec, *ValueSpec, and *TypeSpec. - Spec interface { - Node - specNode() - } - - // An ImportSpec node represents a single package import. - ImportSpec struct { - Doc *CommentGroup // associated documentation; or nil - Name *Ident // local package name (including "."); or nil - Path *BasicLit // import path - Comment *CommentGroup // line comments; or nil - EndPos token.Pos // end of spec (overrides Path.Pos if nonzero) - } - - // A ValueSpec node represents a constant or variable declaration - // (ConstSpec or VarSpec production). - // - ValueSpec struct { - Doc *CommentGroup // associated documentation; or nil - Names []*Ident // value names (len(Names) > 0) - Type Expr // value type; or nil - Values []Expr // initial values; or nil - Comment *CommentGroup // line comments; or nil - } - - // A TypeSpec node represents a type declaration (TypeSpec production). - TypeSpec struct { - Doc *CommentGroup // associated documentation; or nil - Name *Ident // type name - TypeParams *FieldList // type parameters; or nil - Assign token.Pos // position of '=', if any - Type Expr // *Ident, *ParenExpr, *SelectorExpr, *StarExpr, or any of the *XxxTypes - Comment *CommentGroup // line comments; or nil - } -) - -// Pos and End implementations for spec nodes. - -func (s *ImportSpec) Pos() token.Pos { - if s.Name != nil { - return s.Name.Pos() - } - return s.Path.Pos() -} -func (s *ValueSpec) Pos() token.Pos { return s.Names[0].Pos() } -func (s *TypeSpec) Pos() token.Pos { return s.Name.Pos() } - -func (s *ImportSpec) End() token.Pos { - if s.EndPos != 0 { - return s.EndPos - } - return s.Path.End() -} - -func (s *ValueSpec) End() token.Pos { - if n := len(s.Values); n > 0 { - return s.Values[n-1].End() - } - if s.Type != nil { - return s.Type.End() - } - return s.Names[len(s.Names)-1].End() -} -func (s *TypeSpec) End() token.Pos { return s.Type.End() } - -// specNode() ensures that only spec nodes can be -// assigned to a Spec. -func (*ImportSpec) specNode() {} -func (*ValueSpec) specNode() {} -func (*TypeSpec) specNode() {} - -// A declaration is represented by one of the following declaration nodes. -type ( - // A BadDecl node is a placeholder for a declaration containing - // syntax errors for which a correct declaration node cannot be - // created. - // - BadDecl struct { - From, To token.Pos // position range of bad declaration - } - - // A GenDecl node (generic declaration node) represents an import, - // constant, type or variable declaration. A valid Lparen position - // (Lparen.IsValid()) indicates a parenthesized declaration. - // - // Relationship between Tok value and Specs element type: - // - // token.IMPORT *ImportSpec - // token.CONST *ValueSpec - // token.TYPE *TypeSpec - // token.VAR *ValueSpec - // - GenDecl struct { - Doc *CommentGroup // associated documentation; or nil - TokPos token.Pos // position of Tok - Tok token.Token // IMPORT, CONST, TYPE, or VAR - Lparen token.Pos // position of '(', if any - Specs []Spec - Rparen token.Pos // position of ')', if any - } - - // A FuncDecl node represents a function declaration. - FuncDecl struct { - Doc *CommentGroup // associated documentation; or nil - Recv *FieldList // receiver (methods); or nil (functions) - Name *Ident // function/method name - Type *FuncType // function signature: type and value parameters, results, and position of "func" keyword - Body *BlockStmt // function body; or nil for external (non-Go) function - } -) - -// Pos and End implementations for declaration nodes. - -func (d *BadDecl) Pos() token.Pos { return d.From } -func (d *GenDecl) Pos() token.Pos { return d.TokPos } -func (d *FuncDecl) Pos() token.Pos { return d.Type.Pos() } - -func (d *BadDecl) End() token.Pos { return d.To } -func (d *GenDecl) End() token.Pos { - if d.Rparen.IsValid() { - return d.Rparen + 1 - } - return d.Specs[0].End() -} -func (d *FuncDecl) End() token.Pos { - if d.Body != nil { - return d.Body.End() - } - return d.Type.End() -} - -// declNode() ensures that only declaration nodes can be -// assigned to a Decl. -func (*BadDecl) declNode() {} -func (*GenDecl) declNode() {} -func (*FuncDecl) declNode() {} - -// ---------------------------------------------------------------------------- -// Files and packages - -// A File node represents a Go source file. -// -// The Comments list contains all comments in the source file in order of -// appearance, including the comments that are pointed to from other nodes -// via Doc and Comment fields. -// -// For correct printing of source code containing comments (using packages -// go/format and go/printer), special care must be taken to update comments -// when a File's syntax tree is modified: For printing, comments are interspersed -// between tokens based on their position. If syntax tree nodes are -// removed or moved, relevant comments in their vicinity must also be removed -// (from the File.Comments list) or moved accordingly (by updating their -// positions). A CommentMap may be used to facilitate some of these operations. -// -// Whether and how a comment is associated with a node depends on the -// interpretation of the syntax tree by the manipulating program: Except for Doc -// and Comment comments directly associated with nodes, the remaining comments -// are "free-floating" (see also issues #18593, #20744). -type File struct { - Doc *CommentGroup // associated documentation; or nil - Package token.Pos // position of "package" keyword - Name *Ident // package name - Decls []Decl // top-level declarations; or nil - Scope *Scope // package scope (this file only) - Imports []*ImportSpec // imports in this file - Unresolved []*Ident // unresolved identifiers in this file - Comments []*CommentGroup // list of all comments in the source file -} - -func (f *File) Pos() token.Pos { return f.Package } -func (f *File) End() token.Pos { - if n := len(f.Decls); n > 0 { - return f.Decls[n-1].End() - } - return f.Name.End() -} - -// A Package node represents a set of source files -// collectively building a Go package. -type Package struct { - Name string // package name - Scope *Scope // package scope across all files - Imports map[string]*Object // map of package id -> package object - Files map[string]*File // Go source files by filename -} - -func (p *Package) Pos() token.Pos { return token.NoPos } -func (p *Package) End() token.Pos { return token.NoPos } diff --git a/internal/backport/go/ast/ast_test.go b/internal/backport/go/ast/ast_test.go deleted file mode 100644 index 66ae884867..0000000000 --- a/internal/backport/go/ast/ast_test.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2012 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 ast - -import ( - "testing" -) - -var comments = []struct { - list []string - text string -}{ - {[]string{"//"}, ""}, - {[]string{"// "}, ""}, - {[]string{"//", "//", "// "}, ""}, - {[]string{"// foo "}, "foo\n"}, - {[]string{"//", "//", "// foo"}, "foo\n"}, - {[]string{"// foo bar "}, "foo bar\n"}, - {[]string{"// foo", "// bar"}, "foo\nbar\n"}, - {[]string{"// foo", "//", "//", "//", "// bar"}, "foo\n\nbar\n"}, - {[]string{"// foo", "/* bar */"}, "foo\n bar\n"}, - {[]string{"//", "//", "//", "// foo", "//", "//", "//"}, "foo\n"}, - - {[]string{"/**/"}, ""}, - {[]string{"/* */"}, ""}, - {[]string{"/**/", "/**/", "/* */"}, ""}, - {[]string{"/* Foo */"}, " Foo\n"}, - {[]string{"/* Foo Bar */"}, " Foo Bar\n"}, - {[]string{"/* Foo*/", "/* Bar*/"}, " Foo\n Bar\n"}, - {[]string{"/* Foo*/", "/**/", "/**/", "/**/", "// Bar"}, " Foo\n\nBar\n"}, - {[]string{"/* Foo*/", "/*\n*/", "//", "/*\n*/", "// Bar"}, " Foo\n\nBar\n"}, - {[]string{"/* Foo*/", "// Bar"}, " Foo\nBar\n"}, - {[]string{"/* Foo\n Bar*/"}, " Foo\n Bar\n"}, - - {[]string{"// foo", "//go:noinline", "// bar", "//:baz"}, "foo\nbar\n:baz\n"}, - {[]string{"// foo", "//lint123:ignore", "// bar"}, "foo\nbar\n"}, -} - -func TestCommentText(t *testing.T) { - for i, c := range comments { - list := make([]*Comment, len(c.list)) - for i, s := range c.list { - list[i] = &Comment{Text: s} - } - - text := (&CommentGroup{list}).Text() - if text != c.text { - t.Errorf("case %d: got %q; expected %q", i, text, c.text) - } - } -} - -var isDirectiveTests = []struct { - in string - ok bool -}{ - {"abc", false}, - {"go:inline", true}, - {"Go:inline", false}, - {"go:Inline", false}, - {":inline", false}, - {"lint:ignore", true}, - {"lint:1234", true}, - {"1234:lint", true}, - {"go: inline", false}, - {"go:", false}, - {"go:*", false}, - {"go:x*", true}, - {"export foo", true}, - {"extern foo", true}, - {"expert foo", false}, -} - -func TestIsDirective(t *testing.T) { - for _, tt := range isDirectiveTests { - if ok := isDirective(tt.in); ok != tt.ok { - t.Errorf("isDirective(%q) = %v, want %v", tt.in, ok, tt.ok) - } - } -} diff --git a/internal/backport/go/ast/commentmap.go b/internal/backport/go/ast/commentmap.go deleted file mode 100644 index 5e636055ed..0000000000 --- a/internal/backport/go/ast/commentmap.go +++ /dev/null @@ -1,329 +0,0 @@ -// Copyright 2012 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 ast - -import ( - "bytes" - "fmt" - "golang.org/x/website/internal/backport/go/token" - "sort" -) - -type byPos []*CommentGroup - -func (a byPos) Len() int { return len(a) } -func (a byPos) Less(i, j int) bool { return a[i].Pos() < a[j].Pos() } -func (a byPos) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -// sortComments sorts the list of comment groups in source order. -func sortComments(list []*CommentGroup) { - // TODO(gri): Does it make sense to check for sorted-ness - // first (because we know that sorted-ness is - // very likely)? - if orderedList := byPos(list); !sort.IsSorted(orderedList) { - sort.Sort(orderedList) - } -} - -// A CommentMap maps an AST node to a list of comment groups -// associated with it. See NewCommentMap for a description of -// the association. -type CommentMap map[Node][]*CommentGroup - -func (cmap CommentMap) addComment(n Node, c *CommentGroup) { - list := cmap[n] - if len(list) == 0 { - list = []*CommentGroup{c} - } else { - list = append(list, c) - } - cmap[n] = list -} - -type byInterval []Node - -func (a byInterval) Len() int { return len(a) } -func (a byInterval) Less(i, j int) bool { - pi, pj := a[i].Pos(), a[j].Pos() - return pi < pj || pi == pj && a[i].End() > a[j].End() -} -func (a byInterval) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -// nodeList returns the list of nodes of the AST n in source order. -func nodeList(n Node) []Node { - var list []Node - Inspect(n, func(n Node) bool { - // don't collect comments - switch n.(type) { - case nil, *CommentGroup, *Comment: - return false - } - list = append(list, n) - return true - }) - // Note: The current implementation assumes that Inspect traverses the - // AST in depth-first and thus _source_ order. If AST traversal - // does not follow source order, the sorting call below will be - // required. - // sort.Sort(byInterval(list)) - return list -} - -// A commentListReader helps iterating through a list of comment groups. -type commentListReader struct { - fset *token.FileSet - list []*CommentGroup - index int - comment *CommentGroup // comment group at current index - pos, end token.Position // source interval of comment group at current index -} - -func (r *commentListReader) eol() bool { - return r.index >= len(r.list) -} - -func (r *commentListReader) next() { - if !r.eol() { - r.comment = r.list[r.index] - r.pos = r.fset.Position(r.comment.Pos()) - r.end = r.fset.Position(r.comment.End()) - r.index++ - } -} - -// A nodeStack keeps track of nested nodes. -// A node lower on the stack lexically contains the nodes higher on the stack. -type nodeStack []Node - -// push pops all nodes that appear lexically before n -// and then pushes n on the stack. -func (s *nodeStack) push(n Node) { - s.pop(n.Pos()) - *s = append((*s), n) -} - -// pop pops all nodes that appear lexically before pos -// (i.e., whose lexical extent has ended before or at pos). -// It returns the last node popped. -func (s *nodeStack) pop(pos token.Pos) (top Node) { - i := len(*s) - for i > 0 && (*s)[i-1].End() <= pos { - top = (*s)[i-1] - i-- - } - *s = (*s)[0:i] - return top -} - -// NewCommentMap creates a new comment map by associating comment groups -// of the comments list with the nodes of the AST specified by node. -// -// A comment group g is associated with a node n if: -// -// - g starts on the same line as n ends -// - g starts on the line immediately following n, and there is -// at least one empty line after g and before the next node -// - g starts before n and is not associated to the node before n -// via the previous rules -// -// NewCommentMap tries to associate a comment group to the "largest" -// node possible: For instance, if the comment is a line comment -// trailing an assignment, the comment is associated with the entire -// assignment rather than just the last operand in the assignment. -func NewCommentMap(fset *token.FileSet, node Node, comments []*CommentGroup) CommentMap { - if len(comments) == 0 { - return nil // no comments to map - } - - cmap := make(CommentMap) - - // set up comment reader r - tmp := make([]*CommentGroup, len(comments)) - copy(tmp, comments) // don't change incoming comments - sortComments(tmp) - r := commentListReader{fset: fset, list: tmp} // !r.eol() because len(comments) > 0 - r.next() - - // create node list in lexical order - nodes := nodeList(node) - nodes = append(nodes, nil) // append sentinel - - // set up iteration variables - var ( - p Node // previous node - pend token.Position // end of p - pg Node // previous node group (enclosing nodes of "importance") - pgend token.Position // end of pg - stack nodeStack // stack of node groups - ) - - for _, q := range nodes { - var qpos token.Position - if q != nil { - qpos = fset.Position(q.Pos()) // current node position - } else { - // set fake sentinel position to infinity so that - // all comments get processed before the sentinel - const infinity = 1 << 30 - qpos.Offset = infinity - qpos.Line = infinity - } - - // process comments before current node - for r.end.Offset <= qpos.Offset { - // determine recent node group - if top := stack.pop(r.comment.Pos()); top != nil { - pg = top - pgend = fset.Position(pg.End()) - } - // Try to associate a comment first with a node group - // (i.e., a node of "importance" such as a declaration); - // if that fails, try to associate it with the most recent - // node. - // TODO(gri) try to simplify the logic below - var assoc Node - switch { - case pg != nil && - (pgend.Line == r.pos.Line || - pgend.Line+1 == r.pos.Line && r.end.Line+1 < qpos.Line): - // 1) comment starts on same line as previous node group ends, or - // 2) comment starts on the line immediately after the - // previous node group and there is an empty line before - // the current node - // => associate comment with previous node group - assoc = pg - case p != nil && - (pend.Line == r.pos.Line || - pend.Line+1 == r.pos.Line && r.end.Line+1 < qpos.Line || - q == nil): - // same rules apply as above for p rather than pg, - // but also associate with p if we are at the end (q == nil) - assoc = p - default: - // otherwise, associate comment with current node - if q == nil { - // we can only reach here if there was no p - // which would imply that there were no nodes - panic("internal error: no comments should be associated with sentinel") - } - assoc = q - } - cmap.addComment(assoc, r.comment) - if r.eol() { - return cmap - } - r.next() - } - - // update previous node - p = q - pend = fset.Position(p.End()) - - // update previous node group if we see an "important" node - switch q.(type) { - case *File, *Field, Decl, Spec, Stmt: - stack.push(q) - } - } - - return cmap -} - -// Update replaces an old node in the comment map with the new node -// and returns the new node. Comments that were associated with the -// old node are associated with the new node. -func (cmap CommentMap) Update(old, new Node) Node { - if list := cmap[old]; len(list) > 0 { - delete(cmap, old) - cmap[new] = append(cmap[new], list...) - } - return new -} - -// Filter returns a new comment map consisting of only those -// entries of cmap for which a corresponding node exists in -// the AST specified by node. -func (cmap CommentMap) Filter(node Node) CommentMap { - umap := make(CommentMap) - Inspect(node, func(n Node) bool { - if g := cmap[n]; len(g) > 0 { - umap[n] = g - } - return true - }) - return umap -} - -// Comments returns the list of comment groups in the comment map. -// The result is sorted in source order. -func (cmap CommentMap) Comments() []*CommentGroup { - list := make([]*CommentGroup, 0, len(cmap)) - for _, e := range cmap { - list = append(list, e...) - } - sortComments(list) - return list -} - -func summary(list []*CommentGroup) string { - const maxLen = 40 - var buf bytes.Buffer - - // collect comments text -loop: - for _, group := range list { - // Note: CommentGroup.Text() does too much work for what we - // need and would only replace this innermost loop. - // Just do it explicitly. - for _, comment := range group.List { - if buf.Len() >= maxLen { - break loop - } - buf.WriteString(comment.Text) - } - } - - // truncate if too long - if buf.Len() > maxLen { - buf.Truncate(maxLen - 3) - buf.WriteString("...") - } - - // replace any invisibles with blanks - bytes := buf.Bytes() - for i, b := range bytes { - switch b { - case '\t', '\n', '\r': - bytes[i] = ' ' - } - } - - return string(bytes) -} - -func (cmap CommentMap) String() string { - // print map entries in sorted order - var nodes []Node - for node := range cmap { - nodes = append(nodes, node) - } - sort.Sort(byInterval(nodes)) - - var buf bytes.Buffer - fmt.Fprintln(&buf, "CommentMap {") - for _, node := range nodes { - comment := cmap[node] - // print name of identifiers; print node type for other nodes - var s string - if ident, ok := node.(*Ident); ok { - s = ident.Name - } else { - s = fmt.Sprintf("%T", node) - } - fmt.Fprintf(&buf, "\t%p %20s: %s\n", node, s, summary(comment)) - } - fmt.Fprintln(&buf, "}") - return buf.String() -} diff --git a/internal/backport/go/ast/commentmap_test.go b/internal/backport/go/ast/commentmap_test.go deleted file mode 100644 index 67c2daf3c4..0000000000 --- a/internal/backport/go/ast/commentmap_test.go +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright 2012 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. - -// To avoid a cyclic dependency with go/parser, this file is in a separate package. - -package ast_test - -import ( - "bytes" - "fmt" - "sort" - "testing" - - . "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/parser" - "golang.org/x/website/internal/backport/go/token" -) - -const src = ` -// the very first comment - -// package p -package p /* the name is p */ - -// imports -import ( - "bytes" // bytes - "fmt" // fmt - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/parser" -) - -// T -type T struct { - a, b, c int // associated with a, b, c - // associated with x, y - x, y float64 // float values - z complex128 // complex value -} -// also associated with T - -// x -var x = 0 // x = 0 -// also associated with x - -// f1 -func f1() { - /* associated with s1 */ - s1() - // also associated with s1 - - // associated with s2 - - // also associated with s2 - s2() // line comment for s2 -} -// associated with f1 -// also associated with f1 - -// associated with f2 - -// f2 -func f2() { -} - -func f3() { - i := 1 /* 1 */ + 2 // addition - _ = i -} - -// the very last comment -` - -// res maps a key of the form "line number: node type" -// to the associated comments' text. -var res = map[string]string{ - " 5: *ast.File": "the very first comment\npackage p\n", - " 5: *ast.Ident": " the name is p\n", - " 8: *ast.GenDecl": "imports\n", - " 9: *ast.ImportSpec": "bytes\n", - "10: *ast.ImportSpec": "fmt\n", - "16: *ast.GenDecl": "T\nalso associated with T\n", - "17: *ast.Field": "associated with a, b, c\n", - "19: *ast.Field": "associated with x, y\nfloat values\n", - "20: *ast.Field": "complex value\n", - "25: *ast.GenDecl": "x\nx = 0\nalso associated with x\n", - "29: *ast.FuncDecl": "f1\nassociated with f1\nalso associated with f1\n", - "31: *ast.ExprStmt": " associated with s1\nalso associated with s1\n", - "37: *ast.ExprStmt": "associated with s2\nalso associated with s2\nline comment for s2\n", - "45: *ast.FuncDecl": "associated with f2\nf2\n", - "49: *ast.AssignStmt": "addition\n", - "49: *ast.BasicLit": " 1\n", - "50: *ast.Ident": "the very last comment\n", -} - -func ctext(list []*CommentGroup) string { - var buf bytes.Buffer - for _, g := range list { - buf.WriteString(g.Text()) - } - return buf.String() -} - -func TestCommentMap(t *testing.T) { - fset := token.NewFileSet() - f, err := parser.ParseFile(fset, "", src, parser.ParseComments) - if err != nil { - t.Fatal(err) - } - cmap := NewCommentMap(fset, f, f.Comments) - - // very correct association of comments - for n, list := range cmap { - key := fmt.Sprintf("%2d: %T", fset.Position(n.Pos()).Line, n) - got := ctext(list) - want := res[key] - if got != want { - t.Errorf("%s: got %q; want %q", key, got, want) - } - } - - // verify that no comments got lost - if n := len(cmap.Comments()); n != len(f.Comments) { - t.Errorf("got %d comment groups in map; want %d", n, len(f.Comments)) - } - - // support code to update test: - // set genMap to true to generate res map - const genMap = false - if genMap { - out := make([]string, 0, len(cmap)) - for n, list := range cmap { - out = append(out, fmt.Sprintf("\t\"%2d: %T\":\t%q,", fset.Position(n.Pos()).Line, n, ctext(list))) - } - sort.Strings(out) - for _, s := range out { - fmt.Println(s) - } - } -} - -func TestFilter(t *testing.T) { - fset := token.NewFileSet() - f, err := parser.ParseFile(fset, "", src, parser.ParseComments) - if err != nil { - t.Fatal(err) - } - cmap := NewCommentMap(fset, f, f.Comments) - - // delete variable declaration - for i, decl := range f.Decls { - if gen, ok := decl.(*GenDecl); ok && gen.Tok == token.VAR { - copy(f.Decls[i:], f.Decls[i+1:]) - f.Decls = f.Decls[:len(f.Decls)-1] - break - } - } - - // check if comments are filtered correctly - cc := cmap.Filter(f) - for n, list := range cc { - key := fmt.Sprintf("%2d: %T", fset.Position(n.Pos()).Line, n) - got := ctext(list) - want := res[key] - if key == "25: *ast.GenDecl" || got != want { - t.Errorf("%s: got %q; want %q", key, got, want) - } - } -} diff --git a/internal/backport/go/ast/example_test.go b/internal/backport/go/ast/example_test.go deleted file mode 100644 index ae92618aad..0000000000 --- a/internal/backport/go/ast/example_test.go +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright 2012 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 ast_test - -import ( - "bytes" - "fmt" - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/format" - "golang.org/x/website/internal/backport/go/parser" - "golang.org/x/website/internal/backport/go/token" -) - -// This example demonstrates how to inspect the AST of a Go program. -func ExampleInspect() { - // src is the input for which we want to inspect the AST. - src := ` -package p -const c = 1.0 -var X = f(3.14)*2 + c -` - - // Create the AST by parsing src. - fset := token.NewFileSet() // positions are relative to fset - f, err := parser.ParseFile(fset, "src.go", src, 0) - if err != nil { - panic(err) - } - - // Inspect the AST and print all identifiers and literals. - ast.Inspect(f, func(n ast.Node) bool { - var s string - switch x := n.(type) { - case *ast.BasicLit: - s = x.Value - case *ast.Ident: - s = x.Name - } - if s != "" { - fmt.Printf("%s:\t%s\n", fset.Position(n.Pos()), s) - } - return true - }) - - // Output: - // src.go:2:9: p - // src.go:3:7: c - // src.go:3:11: 1.0 - // src.go:4:5: X - // src.go:4:9: f - // src.go:4:11: 3.14 - // src.go:4:17: 2 - // src.go:4:21: c -} - -// This example shows what an AST looks like when printed for debugging. -func ExamplePrint() { - // src is the input for which we want to print the AST. - src := ` -package main -func main() { - println("Hello, World!") -} -` - - // Create the AST by parsing src. - fset := token.NewFileSet() // positions are relative to fset - f, err := parser.ParseFile(fset, "", src, 0) - if err != nil { - panic(err) - } - - // Print the AST. - ast.Print(fset, f) - - // Output: - // 0 *ast.File { - // 1 . Package: 2:1 - // 2 . Name: *ast.Ident { - // 3 . . NamePos: 2:9 - // 4 . . Name: "main" - // 5 . } - // 6 . Decls: []ast.Decl (len = 1) { - // 7 . . 0: *ast.FuncDecl { - // 8 . . . Name: *ast.Ident { - // 9 . . . . NamePos: 3:6 - // 10 . . . . Name: "main" - // 11 . . . . Obj: *ast.Object { - // 12 . . . . . Kind: func - // 13 . . . . . Name: "main" - // 14 . . . . . Decl: *(obj @ 7) - // 15 . . . . } - // 16 . . . } - // 17 . . . Type: *ast.FuncType { - // 18 . . . . Func: 3:1 - // 19 . . . . Params: *ast.FieldList { - // 20 . . . . . Opening: 3:10 - // 21 . . . . . Closing: 3:11 - // 22 . . . . } - // 23 . . . } - // 24 . . . Body: *ast.BlockStmt { - // 25 . . . . Lbrace: 3:13 - // 26 . . . . List: []ast.Stmt (len = 1) { - // 27 . . . . . 0: *ast.ExprStmt { - // 28 . . . . . . X: *ast.CallExpr { - // 29 . . . . . . . Fun: *ast.Ident { - // 30 . . . . . . . . NamePos: 4:2 - // 31 . . . . . . . . Name: "println" - // 32 . . . . . . . } - // 33 . . . . . . . Lparen: 4:9 - // 34 . . . . . . . Args: []ast.Expr (len = 1) { - // 35 . . . . . . . . 0: *ast.BasicLit { - // 36 . . . . . . . . . ValuePos: 4:10 - // 37 . . . . . . . . . Kind: STRING - // 38 . . . . . . . . . Value: "\"Hello, World!\"" - // 39 . . . . . . . . } - // 40 . . . . . . . } - // 41 . . . . . . . Ellipsis: - - // 42 . . . . . . . Rparen: 4:25 - // 43 . . . . . . } - // 44 . . . . . } - // 45 . . . . } - // 46 . . . . Rbrace: 5:1 - // 47 . . . } - // 48 . . } - // 49 . } - // 50 . Scope: *ast.Scope { - // 51 . . Objects: map[string]*ast.Object (len = 1) { - // 52 . . . "main": *(obj @ 11) - // 53 . . } - // 54 . } - // 55 . Unresolved: []*ast.Ident (len = 1) { - // 56 . . 0: *(obj @ 29) - // 57 . } - // 58 } -} - -// This example illustrates how to remove a variable declaration -// in a Go program while maintaining correct comment association -// using an ast.CommentMap. -func ExampleCommentMap() { - // src is the input for which we create the AST that we - // are going to manipulate. - src := ` -// This is the package comment. -package main - -// This comment is associated with the hello constant. -const hello = "Hello, World!" // line comment 1 - -// This comment is associated with the foo variable. -var foo = hello // line comment 2 - -// This comment is associated with the main function. -func main() { - fmt.Println(hello) // line comment 3 -} -` - - // Create the AST by parsing src. - fset := token.NewFileSet() // positions are relative to fset - f, err := parser.ParseFile(fset, "src.go", src, parser.ParseComments) - if err != nil { - panic(err) - } - - // Create an ast.CommentMap from the ast.File's comments. - // This helps keeping the association between comments - // and AST nodes. - cmap := ast.NewCommentMap(fset, f, f.Comments) - - // Remove the first variable declaration from the list of declarations. - for i, decl := range f.Decls { - if gen, ok := decl.(*ast.GenDecl); ok && gen.Tok == token.VAR { - copy(f.Decls[i:], f.Decls[i+1:]) - f.Decls = f.Decls[:len(f.Decls)-1] - break - } - } - - // Use the comment map to filter comments that don't belong anymore - // (the comments associated with the variable declaration), and create - // the new comments list. - f.Comments = cmap.Filter(f).Comments() - - // Print the modified AST. - var buf bytes.Buffer - if err := format.Node(&buf, fset, f); err != nil { - panic(err) - } - fmt.Printf("%s", buf.Bytes()) - - // Output: - // // This is the package comment. - // package main - // - // // This comment is associated with the hello constant. - // const hello = "Hello, World!" // line comment 1 - // - // // This comment is associated with the main function. - // func main() { - // fmt.Println(hello) // line comment 3 - // } -} diff --git a/internal/backport/go/ast/filter.go b/internal/backport/go/ast/filter.go deleted file mode 100644 index 8c2ed9d267..0000000000 --- a/internal/backport/go/ast/filter.go +++ /dev/null @@ -1,488 +0,0 @@ -// Copyright 2009 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 ast - -import ( - "golang.org/x/website/internal/backport/go/token" - "sort" -) - -// ---------------------------------------------------------------------------- -// Export filtering - -// exportFilter is a special filter function to extract exported nodes. -func exportFilter(name string) bool { - return IsExported(name) -} - -// FileExports trims the AST for a Go source file in place such that -// only exported nodes remain: all top-level identifiers which are not exported -// and their associated information (such as type, initial value, or function -// body) are removed. Non-exported fields and methods of exported types are -// stripped. The File.Comments list is not changed. -// -// FileExports reports whether there are exported declarations. -func FileExports(src *File) bool { - return filterFile(src, exportFilter, true) -} - -// PackageExports trims the AST for a Go package in place such that -// only exported nodes remain. The pkg.Files list is not changed, so that -// file names and top-level package comments don't get lost. -// -// PackageExports reports whether there are exported declarations; -// it returns false otherwise. -func PackageExports(pkg *Package) bool { - return filterPackage(pkg, exportFilter, true) -} - -// ---------------------------------------------------------------------------- -// General filtering - -type Filter func(string) bool - -func filterIdentList(list []*Ident, f Filter) []*Ident { - j := 0 - for _, x := range list { - if f(x.Name) { - list[j] = x - j++ - } - } - return list[0:j] -} - -// fieldName assumes that x is the type of an anonymous field and -// returns the corresponding field name. If x is not an acceptable -// anonymous field, the result is nil. -func fieldName(x Expr) *Ident { - switch t := x.(type) { - case *Ident: - return t - case *SelectorExpr: - if _, ok := t.X.(*Ident); ok { - return t.Sel - } - case *StarExpr: - return fieldName(t.X) - } - return nil -} - -func filterFieldList(fields *FieldList, filter Filter, export bool) (removedFields bool) { - if fields == nil { - return false - } - list := fields.List - j := 0 - for _, f := range list { - keepField := false - if len(f.Names) == 0 { - // anonymous field - name := fieldName(f.Type) - keepField = name != nil && filter(name.Name) - } else { - n := len(f.Names) - f.Names = filterIdentList(f.Names, filter) - if len(f.Names) < n { - removedFields = true - } - keepField = len(f.Names) > 0 - } - if keepField { - if export { - filterType(f.Type, filter, export) - } - list[j] = f - j++ - } - } - if j < len(list) { - removedFields = true - } - fields.List = list[0:j] - return -} - -func filterCompositeLit(lit *CompositeLit, filter Filter, export bool) { - n := len(lit.Elts) - lit.Elts = filterExprList(lit.Elts, filter, export) - if len(lit.Elts) < n { - lit.Incomplete = true - } -} - -func filterExprList(list []Expr, filter Filter, export bool) []Expr { - j := 0 - for _, exp := range list { - switch x := exp.(type) { - case *CompositeLit: - filterCompositeLit(x, filter, export) - case *KeyValueExpr: - if x, ok := x.Key.(*Ident); ok && !filter(x.Name) { - continue - } - if x, ok := x.Value.(*CompositeLit); ok { - filterCompositeLit(x, filter, export) - } - } - list[j] = exp - j++ - } - return list[0:j] -} - -func filterParamList(fields *FieldList, filter Filter, export bool) bool { - if fields == nil { - return false - } - var b bool - for _, f := range fields.List { - if filterType(f.Type, filter, export) { - b = true - } - } - return b -} - -func filterType(typ Expr, f Filter, export bool) bool { - switch t := typ.(type) { - case *Ident: - return f(t.Name) - case *ParenExpr: - return filterType(t.X, f, export) - case *ArrayType: - return filterType(t.Elt, f, export) - case *StructType: - if filterFieldList(t.Fields, f, export) { - t.Incomplete = true - } - return len(t.Fields.List) > 0 - case *FuncType: - b1 := filterParamList(t.Params, f, export) - b2 := filterParamList(t.Results, f, export) - return b1 || b2 - case *InterfaceType: - if filterFieldList(t.Methods, f, export) { - t.Incomplete = true - } - return len(t.Methods.List) > 0 - case *MapType: - b1 := filterType(t.Key, f, export) - b2 := filterType(t.Value, f, export) - return b1 || b2 - case *ChanType: - return filterType(t.Value, f, export) - } - return false -} - -func filterSpec(spec Spec, f Filter, export bool) bool { - switch s := spec.(type) { - case *ValueSpec: - s.Names = filterIdentList(s.Names, f) - s.Values = filterExprList(s.Values, f, export) - if len(s.Names) > 0 { - if export { - filterType(s.Type, f, export) - } - return true - } - case *TypeSpec: - if f(s.Name.Name) { - if export { - filterType(s.Type, f, export) - } - return true - } - if !export { - // For general filtering (not just exports), - // filter type even if name is not filtered - // out. - // If the type contains filtered elements, - // keep the declaration. - return filterType(s.Type, f, export) - } - } - return false -} - -func filterSpecList(list []Spec, f Filter, export bool) []Spec { - j := 0 - for _, s := range list { - if filterSpec(s, f, export) { - list[j] = s - j++ - } - } - return list[0:j] -} - -// FilterDecl trims the AST for a Go declaration in place by removing -// all names (including struct field and interface method names, but -// not from parameter lists) that don't pass through the filter f. -// -// FilterDecl reports whether there are any declared names left after -// filtering. -func FilterDecl(decl Decl, f Filter) bool { - return filterDecl(decl, f, false) -} - -func filterDecl(decl Decl, f Filter, export bool) bool { - switch d := decl.(type) { - case *GenDecl: - d.Specs = filterSpecList(d.Specs, f, export) - return len(d.Specs) > 0 - case *FuncDecl: - return f(d.Name.Name) - } - return false -} - -// FilterFile trims the AST for a Go file in place by removing all -// names from top-level declarations (including struct field and -// interface method names, but not from parameter lists) that don't -// pass through the filter f. If the declaration is empty afterwards, -// the declaration is removed from the AST. Import declarations are -// always removed. The File.Comments list is not changed. -// -// FilterFile reports whether there are any top-level declarations -// left after filtering. -func FilterFile(src *File, f Filter) bool { - return filterFile(src, f, false) -} - -func filterFile(src *File, f Filter, export bool) bool { - j := 0 - for _, d := range src.Decls { - if filterDecl(d, f, export) { - src.Decls[j] = d - j++ - } - } - src.Decls = src.Decls[0:j] - return j > 0 -} - -// FilterPackage trims the AST for a Go package in place by removing -// all names from top-level declarations (including struct field and -// interface method names, but not from parameter lists) that don't -// pass through the filter f. If the declaration is empty afterwards, -// the declaration is removed from the AST. The pkg.Files list is not -// changed, so that file names and top-level package comments don't get -// lost. -// -// FilterPackage reports whether there are any top-level declarations -// left after filtering. -func FilterPackage(pkg *Package, f Filter) bool { - return filterPackage(pkg, f, false) -} - -func filterPackage(pkg *Package, f Filter, export bool) bool { - hasDecls := false - for _, src := range pkg.Files { - if filterFile(src, f, export) { - hasDecls = true - } - } - return hasDecls -} - -// ---------------------------------------------------------------------------- -// Merging of package files - -// The MergeMode flags control the behavior of MergePackageFiles. -type MergeMode uint - -const ( - // If set, duplicate function declarations are excluded. - FilterFuncDuplicates MergeMode = 1 << iota - // If set, comments that are not associated with a specific - // AST node (as Doc or Comment) are excluded. - FilterUnassociatedComments - // If set, duplicate import declarations are excluded. - FilterImportDuplicates -) - -// nameOf returns the function (foo) or method name (foo.bar) for -// the given function declaration. If the AST is incorrect for the -// receiver, it assumes a function instead. -func nameOf(f *FuncDecl) string { - if r := f.Recv; r != nil && len(r.List) == 1 { - // looks like a correct receiver declaration - t := r.List[0].Type - // dereference pointer receiver types - if p, _ := t.(*StarExpr); p != nil { - t = p.X - } - // the receiver type must be a type name - if p, _ := t.(*Ident); p != nil { - return p.Name + "." + f.Name.Name - } - // otherwise assume a function instead - } - return f.Name.Name -} - -// separator is an empty //-style comment that is interspersed between -// different comment groups when they are concatenated into a single group -var separator = &Comment{token.NoPos, "//"} - -// MergePackageFiles creates a file AST by merging the ASTs of the -// files belonging to a package. The mode flags control merging behavior. -func MergePackageFiles(pkg *Package, mode MergeMode) *File { - // Count the number of package docs, comments and declarations across - // all package files. Also, compute sorted list of filenames, so that - // subsequent iterations can always iterate in the same order. - ndocs := 0 - ncomments := 0 - ndecls := 0 - filenames := make([]string, len(pkg.Files)) - i := 0 - for filename, f := range pkg.Files { - filenames[i] = filename - i++ - if f.Doc != nil { - ndocs += len(f.Doc.List) + 1 // +1 for separator - } - ncomments += len(f.Comments) - ndecls += len(f.Decls) - } - sort.Strings(filenames) - - // Collect package comments from all package files into a single - // CommentGroup - the collected package documentation. In general - // there should be only one file with a package comment; but it's - // better to collect extra comments than drop them on the floor. - var doc *CommentGroup - var pos token.Pos - if ndocs > 0 { - list := make([]*Comment, ndocs-1) // -1: no separator before first group - i := 0 - for _, filename := range filenames { - f := pkg.Files[filename] - if f.Doc != nil { - if i > 0 { - // not the first group - add separator - list[i] = separator - i++ - } - for _, c := range f.Doc.List { - list[i] = c - i++ - } - if f.Package > pos { - // Keep the maximum package clause position as - // position for the package clause of the merged - // files. - pos = f.Package - } - } - } - doc = &CommentGroup{list} - } - - // Collect declarations from all package files. - var decls []Decl - if ndecls > 0 { - decls = make([]Decl, ndecls) - funcs := make(map[string]int) // map of func name -> decls index - i := 0 // current index - n := 0 // number of filtered entries - for _, filename := range filenames { - f := pkg.Files[filename] - for _, d := range f.Decls { - if mode&FilterFuncDuplicates != 0 { - // A language entity may be declared multiple - // times in different package files; only at - // build time declarations must be unique. - // For now, exclude multiple declarations of - // functions - keep the one with documentation. - // - // TODO(gri): Expand this filtering to other - // entities (const, type, vars) if - // multiple declarations are common. - if f, isFun := d.(*FuncDecl); isFun { - name := nameOf(f) - if j, exists := funcs[name]; exists { - // function declared already - if decls[j] != nil && decls[j].(*FuncDecl).Doc == nil { - // existing declaration has no documentation; - // ignore the existing declaration - decls[j] = nil - } else { - // ignore the new declaration - d = nil - } - n++ // filtered an entry - } else { - funcs[name] = i - } - } - } - decls[i] = d - i++ - } - } - - // Eliminate nil entries from the decls list if entries were - // filtered. We do this using a 2nd pass in order to not disturb - // the original declaration order in the source (otherwise, this - // would also invalidate the monotonically increasing position - // info within a single file). - if n > 0 { - i = 0 - for _, d := range decls { - if d != nil { - decls[i] = d - i++ - } - } - decls = decls[0:i] - } - } - - // Collect import specs from all package files. - var imports []*ImportSpec - if mode&FilterImportDuplicates != 0 { - seen := make(map[string]bool) - for _, filename := range filenames { - f := pkg.Files[filename] - for _, imp := range f.Imports { - if path := imp.Path.Value; !seen[path] { - // TODO: consider handling cases where: - // - 2 imports exist with the same import path but - // have different local names (one should probably - // keep both of them) - // - 2 imports exist but only one has a comment - // - 2 imports exist and they both have (possibly - // different) comments - imports = append(imports, imp) - seen[path] = true - } - } - } - } else { - // Iterate over filenames for deterministic order. - for _, filename := range filenames { - f := pkg.Files[filename] - imports = append(imports, f.Imports...) - } - } - - // Collect comments from all package files. - var comments []*CommentGroup - if mode&FilterUnassociatedComments == 0 { - comments = make([]*CommentGroup, ncomments) - i := 0 - for _, filename := range filenames { - f := pkg.Files[filename] - i += copy(comments[i:], f.Comments) - } - } - - // TODO(gri) need to compute unresolved identifiers! - return &File{doc, pos, NewIdent(pkg.Name), decls, pkg.Scope, imports, nil, comments} -} diff --git a/internal/backport/go/ast/filter_test.go b/internal/backport/go/ast/filter_test.go deleted file mode 100644 index fa5da6a1ab..0000000000 --- a/internal/backport/go/ast/filter_test.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2013 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. - -// To avoid a cyclic dependency with go/parser, this file is in a separate package. - -package ast_test - -import ( - "bytes" - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/format" - "golang.org/x/website/internal/backport/go/parser" - "golang.org/x/website/internal/backport/go/token" - "testing" -) - -const input = `package p - -type t1 struct{} -type t2 struct{} - -func f1() {} -func f1() {} -func f2() {} - -func (*t1) f1() {} -func (t1) f1() {} -func (t1) f2() {} - -func (t2) f1() {} -func (t2) f2() {} -func (x *t2) f2() {} -` - -// Calling ast.MergePackageFiles with ast.FilterFuncDuplicates -// keeps a duplicate entry with attached documentation in favor -// of one without, and it favors duplicate entries appearing -// later in the source over ones appearing earlier. This is why -// (*t2).f2 is kept and t2.f2 is eliminated in this test case. -const golden = `package p - -type t1 struct{} -type t2 struct{} - -func f1() {} -func f2() {} - -func (t1) f1() {} -func (t1) f2() {} - -func (t2) f1() {} - -func (x *t2) f2() {} -` - -func TestFilterDuplicates(t *testing.T) { - // parse input - fset := token.NewFileSet() - file, err := parser.ParseFile(fset, "", input, 0) - if err != nil { - t.Fatal(err) - } - - // create package - files := map[string]*ast.File{"": file} - pkg, err := ast.NewPackage(fset, files, nil, nil) - if err != nil { - t.Fatal(err) - } - - // filter - merged := ast.MergePackageFiles(pkg, ast.FilterFuncDuplicates) - - // pretty-print - var buf bytes.Buffer - if err := format.Node(&buf, fset, merged); err != nil { - t.Fatal(err) - } - output := buf.String() - - if output != golden { - t.Errorf("incorrect output:\n%s", output) - } -} diff --git a/internal/backport/go/ast/import.go b/internal/backport/go/ast/import.go deleted file mode 100644 index 7c70ef6696..0000000000 --- a/internal/backport/go/ast/import.go +++ /dev/null @@ -1,230 +0,0 @@ -// Copyright 2011 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 ast - -import ( - "golang.org/x/website/internal/backport/go/token" - "sort" - "strconv" -) - -// SortImports sorts runs of consecutive import lines in import blocks in f. -// It also removes duplicate imports when it is possible to do so without data loss. -func SortImports(fset *token.FileSet, f *File) { - for _, d := range f.Decls { - d, ok := d.(*GenDecl) - if !ok || d.Tok != token.IMPORT { - // Not an import declaration, so we're done. - // Imports are always first. - break - } - - if !d.Lparen.IsValid() { - // Not a block: sorted by default. - continue - } - - // Identify and sort runs of specs on successive lines. - i := 0 - specs := d.Specs[:0] - for j, s := range d.Specs { - if j > i && lineAt(fset, s.Pos()) > 1+lineAt(fset, d.Specs[j-1].End()) { - // j begins a new run. End this one. - specs = append(specs, sortSpecs(fset, f, d.Specs[i:j])...) - i = j - } - } - specs = append(specs, sortSpecs(fset, f, d.Specs[i:])...) - d.Specs = specs - - // Deduping can leave a blank line before the rparen; clean that up. - if len(d.Specs) > 0 { - lastSpec := d.Specs[len(d.Specs)-1] - lastLine := lineAt(fset, lastSpec.Pos()) - rParenLine := lineAt(fset, d.Rparen) - for rParenLine > lastLine+1 { - rParenLine-- - fset.File(d.Rparen).MergeLine(rParenLine) - } - } - } -} - -func lineAt(fset *token.FileSet, pos token.Pos) int { - return fset.PositionFor(pos, false).Line -} - -func importPath(s Spec) string { - t, err := strconv.Unquote(s.(*ImportSpec).Path.Value) - if err == nil { - return t - } - return "" -} - -func importName(s Spec) string { - n := s.(*ImportSpec).Name - if n == nil { - return "" - } - return n.Name -} - -func importComment(s Spec) string { - c := s.(*ImportSpec).Comment - if c == nil { - return "" - } - return c.Text() -} - -// collapse indicates whether prev may be removed, leaving only next. -func collapse(prev, next Spec) bool { - if importPath(next) != importPath(prev) || importName(next) != importName(prev) { - return false - } - return prev.(*ImportSpec).Comment == nil -} - -type posSpan struct { - Start token.Pos - End token.Pos -} - -type cgPos struct { - left bool // true if comment is to the left of the spec, false otherwise. - cg *CommentGroup -} - -func sortSpecs(fset *token.FileSet, f *File, specs []Spec) []Spec { - // Can't short-circuit here even if specs are already sorted, - // since they might yet need deduplication. - // A lone import, however, may be safely ignored. - if len(specs) <= 1 { - return specs - } - - // Record positions for specs. - pos := make([]posSpan, len(specs)) - for i, s := range specs { - pos[i] = posSpan{s.Pos(), s.End()} - } - - // Identify comments in this range. - begSpecs := pos[0].Start - endSpecs := pos[len(pos)-1].End - beg := fset.File(begSpecs).LineStart(lineAt(fset, begSpecs)) - endLine := lineAt(fset, endSpecs) - endFile := fset.File(endSpecs) - var end token.Pos - if endLine == endFile.LineCount() { - end = endSpecs - } else { - end = endFile.LineStart(endLine + 1) // beginning of next line - } - first := len(f.Comments) - last := -1 - for i, g := range f.Comments { - if g.End() >= end { - break - } - // g.End() < end - if beg <= g.Pos() { - // comment is within the range [beg, end[ of import declarations - if i < first { - first = i - } - if i > last { - last = i - } - } - } - - var comments []*CommentGroup - if last >= 0 { - comments = f.Comments[first : last+1] - } - - // Assign each comment to the import spec on the same line. - importComments := map[*ImportSpec][]cgPos{} - specIndex := 0 - for _, g := range comments { - for specIndex+1 < len(specs) && pos[specIndex+1].Start <= g.Pos() { - specIndex++ - } - var left bool - // A block comment can appear before the first import spec. - if specIndex == 0 && pos[specIndex].Start > g.Pos() { - left = true - } else if specIndex+1 < len(specs) && // Or it can appear on the left of an import spec. - lineAt(fset, pos[specIndex].Start)+1 == lineAt(fset, g.Pos()) { - specIndex++ - left = true - } - s := specs[specIndex].(*ImportSpec) - importComments[s] = append(importComments[s], cgPos{left: left, cg: g}) - } - - // Sort the import specs by import path. - // Remove duplicates, when possible without data loss. - // Reassign the import paths to have the same position sequence. - // Reassign each comment to the spec on the same line. - // Sort the comments by new position. - sort.Slice(specs, func(i, j int) bool { - ipath := importPath(specs[i]) - jpath := importPath(specs[j]) - if ipath != jpath { - return ipath < jpath - } - iname := importName(specs[i]) - jname := importName(specs[j]) - if iname != jname { - return iname < jname - } - return importComment(specs[i]) < importComment(specs[j]) - }) - - // Dedup. Thanks to our sorting, we can just consider - // adjacent pairs of imports. - deduped := specs[:0] - for i, s := range specs { - if i == len(specs)-1 || !collapse(s, specs[i+1]) { - deduped = append(deduped, s) - } else { - p := s.Pos() - fset.File(p).MergeLine(lineAt(fset, p)) - } - } - specs = deduped - - // Fix up comment positions - for i, s := range specs { - s := s.(*ImportSpec) - if s.Name != nil { - s.Name.NamePos = pos[i].Start - } - s.Path.ValuePos = pos[i].Start - s.EndPos = pos[i].End - for _, g := range importComments[s] { - for _, c := range g.cg.List { - if g.left { - c.Slash = pos[i].Start - 1 - } else { - // An import spec can have both block comment and a line comment - // to its right. In that case, both of them will have the same pos. - // But while formatting the AST, the line comment gets moved to - // after the block comment. - c.Slash = pos[i].End - } - } - } - } - - sort.Slice(comments, func(i, j int) bool { - return comments[i].Pos() < comments[j].Pos() - }) - - return specs -} diff --git a/internal/backport/go/ast/issues_test.go b/internal/backport/go/ast/issues_test.go deleted file mode 100644 index 3fb32053af..0000000000 --- a/internal/backport/go/ast/issues_test.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2019 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 ast_test - -import ( - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/parser" - "golang.org/x/website/internal/backport/go/token" - "testing" -) - -func TestIssue33649(t *testing.T) { - for _, src := range []string{ - `package p; func _()`, - `package p; func _() {`, - `package p; func _() { _ = 0`, - `package p; func _() { _ = 0 }`, - } { - fset := token.NewFileSet() - f, _ := parser.ParseFile(fset, "", src, parser.AllErrors) - if f == nil { - panic("invalid test setup: parser didn't return an AST") - } - - // find corresponding token.File - var tf *token.File - fset.Iterate(func(f *token.File) bool { - tf = f - return true - }) - tfEnd := tf.Base() + tf.Size() - - fd := f.Decls[len(f.Decls)-1].(*ast.FuncDecl) - fdEnd := int(fd.End()) - - if fdEnd != tfEnd { - t.Errorf("%q: got fdEnd = %d; want %d (base = %d, size = %d)", src, fdEnd, tfEnd, tf.Base(), tf.Size()) - } - } -} diff --git a/internal/backport/go/ast/print.go b/internal/backport/go/ast/print.go deleted file mode 100644 index 16ce365122..0000000000 --- a/internal/backport/go/ast/print.go +++ /dev/null @@ -1,255 +0,0 @@ -// Copyright 2010 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. - -// This file contains printing support for ASTs. - -package ast - -import ( - "fmt" - "io" - "os" - "reflect" - - "golang.org/x/website/internal/backport/go/token" -) - -// A FieldFilter may be provided to Fprint to control the output. -type FieldFilter func(name string, value reflect.Value) bool - -// NotNilFilter returns true for field values that are not nil; -// it returns false otherwise. -func NotNilFilter(_ string, v reflect.Value) bool { - switch v.Kind() { - case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: - return !v.IsNil() - } - return true -} - -// Fprint prints the (sub-)tree starting at AST node x to w. -// If fset != nil, position information is interpreted relative -// to that file set. Otherwise positions are printed as integer -// values (file set specific offsets). -// -// A non-nil FieldFilter f may be provided to control the output: -// struct fields for which f(fieldname, fieldvalue) is true are -// printed; all others are filtered from the output. Unexported -// struct fields are never printed. -func Fprint(w io.Writer, fset *token.FileSet, x interface{}, f FieldFilter) error { - return fprint(w, fset, x, f) -} - -func fprint(w io.Writer, fset *token.FileSet, x interface{}, f FieldFilter) (err error) { - // setup printer - p := printer{ - output: w, - fset: fset, - filter: f, - ptrmap: make(map[interface{}]int), - last: '\n', // force printing of line number on first line - } - - // install error handler - defer func() { - if e := recover(); e != nil { - err = e.(localError).err // re-panics if it's not a localError - } - }() - - // print x - if x == nil { - p.printf("nil\n") - return - } - p.print(reflect.ValueOf(x)) - p.printf("\n") - - return -} - -// Print prints x to standard output, skipping nil fields. -// Print(fset, x) is the same as Fprint(os.Stdout, fset, x, NotNilFilter). -func Print(fset *token.FileSet, x interface{}) error { - return Fprint(os.Stdout, fset, x, NotNilFilter) -} - -type printer struct { - output io.Writer - fset *token.FileSet - filter FieldFilter - ptrmap map[interface{}]int // *T -> line number - indent int // current indentation level - last byte // the last byte processed by Write - line int // current line number -} - -var indent = []byte(". ") - -func (p *printer) Write(data []byte) (n int, err error) { - var m int - for i, b := range data { - // invariant: data[0:n] has been written - if b == '\n' { - m, err = p.output.Write(data[n : i+1]) - n += m - if err != nil { - return - } - p.line++ - } else if p.last == '\n' { - _, err = fmt.Fprintf(p.output, "%6d ", p.line) - if err != nil { - return - } - for j := p.indent; j > 0; j-- { - _, err = p.output.Write(indent) - if err != nil { - return - } - } - } - p.last = b - } - if len(data) > n { - m, err = p.output.Write(data[n:]) - n += m - } - return -} - -// localError wraps locally caught errors so we can distinguish -// them from genuine panics which we don't want to return as errors. -type localError struct { - err error -} - -// printf is a convenience wrapper that takes care of print errors. -func (p *printer) printf(format string, args ...interface{}) { - if _, err := fmt.Fprintf(p, format, args...); err != nil { - panic(localError{err}) - } -} - -// Implementation note: Print is written for AST nodes but could be -// used to print arbitrary data structures; such a version should -// probably be in a different package. -// -// Note: This code detects (some) cycles created via pointers but -// not cycles that are created via slices or maps containing the -// same slice or map. Code for general data structures probably -// should catch those as well. - -func (p *printer) print(x reflect.Value) { - if !NotNilFilter("", x) { - p.printf("nil") - return - } - - switch x.Kind() { - case reflect.Interface: - p.print(x.Elem()) - - case reflect.Map: - p.printf("%s (len = %d) {", x.Type(), x.Len()) - if x.Len() > 0 { - p.indent++ - p.printf("\n") - for _, key := range x.MapKeys() { - p.print(key) - p.printf(": ") - p.print(x.MapIndex(key)) - p.printf("\n") - } - p.indent-- - } - p.printf("}") - - case reflect.Ptr: - p.printf("*") - // type-checked ASTs may contain cycles - use ptrmap - // to keep track of objects that have been printed - // already and print the respective line number instead - ptr := x.Interface() - if line, exists := p.ptrmap[ptr]; exists { - p.printf("(obj @ %d)", line) - } else { - p.ptrmap[ptr] = p.line - p.print(x.Elem()) - } - - case reflect.Array: - p.printf("%s {", x.Type()) - if x.Len() > 0 { - p.indent++ - p.printf("\n") - for i, n := 0, x.Len(); i < n; i++ { - p.printf("%d: ", i) - p.print(x.Index(i)) - p.printf("\n") - } - p.indent-- - } - p.printf("}") - - case reflect.Slice: - if s, ok := x.Interface().([]byte); ok { - p.printf("%#q", s) - return - } - p.printf("%s (len = %d) {", x.Type(), x.Len()) - if x.Len() > 0 { - p.indent++ - p.printf("\n") - for i, n := 0, x.Len(); i < n; i++ { - p.printf("%d: ", i) - p.print(x.Index(i)) - p.printf("\n") - } - p.indent-- - } - p.printf("}") - - case reflect.Struct: - t := x.Type() - p.printf("%s {", t) - p.indent++ - first := true - for i, n := 0, t.NumField(); i < n; i++ { - // exclude non-exported fields because their - // values cannot be accessed via reflection - if name := t.Field(i).Name; IsExported(name) { - value := x.Field(i) - if p.filter == nil || p.filter(name, value) { - if first { - p.printf("\n") - first = false - } - p.printf("%s: ", name) - p.print(value) - p.printf("\n") - } - } - } - p.indent-- - p.printf("}") - - default: - v := x.Interface() - switch v := v.(type) { - case string: - // print strings in quotes - p.printf("%q", v) - return - case token.Pos: - // position values can be printed nicely if we have a file set - if p.fset != nil { - p.printf("%s", p.fset.Position(v)) - return - } - } - // default - p.printf("%v", v) - } -} diff --git a/internal/backport/go/ast/print_test.go b/internal/backport/go/ast/print_test.go deleted file mode 100644 index 210f164301..0000000000 --- a/internal/backport/go/ast/print_test.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2011 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 ast - -import ( - "bytes" - "strings" - "testing" -) - -var tests = []struct { - x interface{} // x is printed as s - s string -}{ - // basic types - {nil, "0 nil"}, - {true, "0 true"}, - {42, "0 42"}, - {3.14, "0 3.14"}, - {1 + 2.718i, "0 (1+2.718i)"}, - {"foobar", "0 \"foobar\""}, - - // maps - {map[Expr]string{}, `0 map[ast.Expr]string (len = 0) {}`}, - {map[string]int{"a": 1}, - `0 map[string]int (len = 1) { - 1 . "a": 1 - 2 }`}, - - // pointers - {new(int), "0 *0"}, - - // arrays - {[0]int{}, `0 [0]int {}`}, - {[3]int{1, 2, 3}, - `0 [3]int { - 1 . 0: 1 - 2 . 1: 2 - 3 . 2: 3 - 4 }`}, - {[...]int{42}, - `0 [1]int { - 1 . 0: 42 - 2 }`}, - - // slices - {[]int{}, `0 []int (len = 0) {}`}, - {[]int{1, 2, 3}, - `0 []int (len = 3) { - 1 . 0: 1 - 2 . 1: 2 - 3 . 2: 3 - 4 }`}, - - // structs - {struct{}{}, `0 struct {} {}`}, - {struct{ x int }{007}, `0 struct { x int } {}`}, - {struct{ X, y int }{42, 991}, - `0 struct { X int; y int } { - 1 . X: 42 - 2 }`}, - {struct{ X, Y int }{42, 991}, - `0 struct { X int; Y int } { - 1 . X: 42 - 2 . Y: 991 - 3 }`}, -} - -// Split s into lines, trim whitespace from all lines, and return -// the concatenated non-empty lines. -func trim(s string) string { - lines := strings.Split(s, "\n") - i := 0 - for _, line := range lines { - line = strings.TrimSpace(line) - if line != "" { - lines[i] = line - i++ - } - } - return strings.Join(lines[0:i], "\n") -} - -func TestPrint(t *testing.T) { - var buf bytes.Buffer - for _, test := range tests { - buf.Reset() - if err := Fprint(&buf, nil, test.x, nil); err != nil { - t.Errorf("Fprint failed: %s", err) - } - if s, ts := trim(buf.String()), trim(test.s); s != ts { - t.Errorf("got:\n%s\nexpected:\n%s\n", s, ts) - } - } -} diff --git a/internal/backport/go/ast/resolve.go b/internal/backport/go/ast/resolve.go deleted file mode 100644 index 4d3d3fdc01..0000000000 --- a/internal/backport/go/ast/resolve.go +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright 2011 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. - -// This file implements NewPackage. - -package ast - -import ( - "fmt" - "golang.org/x/website/internal/backport/go/scanner" - "golang.org/x/website/internal/backport/go/token" - "strconv" -) - -type pkgBuilder struct { - fset *token.FileSet - errors scanner.ErrorList -} - -func (p *pkgBuilder) error(pos token.Pos, msg string) { - p.errors.Add(p.fset.Position(pos), msg) -} - -func (p *pkgBuilder) errorf(pos token.Pos, format string, args ...interface{}) { - p.error(pos, fmt.Sprintf(format, args...)) -} - -func (p *pkgBuilder) declare(scope, altScope *Scope, obj *Object) { - alt := scope.Insert(obj) - if alt == nil && altScope != nil { - // see if there is a conflicting declaration in altScope - alt = altScope.Lookup(obj.Name) - } - if alt != nil { - prevDecl := "" - if pos := alt.Pos(); pos.IsValid() { - prevDecl = fmt.Sprintf("\n\tprevious declaration at %s", p.fset.Position(pos)) - } - p.error(obj.Pos(), fmt.Sprintf("%s redeclared in this block%s", obj.Name, prevDecl)) - } -} - -func resolve(scope *Scope, ident *Ident) bool { - for ; scope != nil; scope = scope.Outer { - if obj := scope.Lookup(ident.Name); obj != nil { - ident.Obj = obj - return true - } - } - return false -} - -// An Importer resolves import paths to package Objects. -// The imports map records the packages already imported, -// indexed by package id (canonical import path). -// An Importer must determine the canonical import path and -// check the map to see if it is already present in the imports map. -// If so, the Importer can return the map entry. Otherwise, the -// Importer should load the package data for the given path into -// a new *Object (pkg), record pkg in the imports map, and then -// return pkg. -type Importer func(imports map[string]*Object, path string) (pkg *Object, err error) - -// NewPackage creates a new Package node from a set of File nodes. It resolves -// unresolved identifiers across files and updates each file's Unresolved list -// accordingly. If a non-nil importer and universe scope are provided, they are -// used to resolve identifiers not declared in any of the package files. Any -// remaining unresolved identifiers are reported as undeclared. If the files -// belong to different packages, one package name is selected and files with -// different package names are reported and then ignored. -// The result is a package node and a scanner.ErrorList if there were errors. -func NewPackage(fset *token.FileSet, files map[string]*File, importer Importer, universe *Scope) (*Package, error) { - var p pkgBuilder - p.fset = fset - - // complete package scope - pkgName := "" - pkgScope := NewScope(universe) - for _, file := range files { - // package names must match - switch name := file.Name.Name; { - case pkgName == "": - pkgName = name - case name != pkgName: - p.errorf(file.Package, "package %s; expected %s", name, pkgName) - continue // ignore this file - } - - // collect top-level file objects in package scope - for _, obj := range file.Scope.Objects { - p.declare(pkgScope, nil, obj) - } - } - - // package global mapping of imported package ids to package objects - imports := make(map[string]*Object) - - // complete file scopes with imports and resolve identifiers - for _, file := range files { - // ignore file if it belongs to a different package - // (error has already been reported) - if file.Name.Name != pkgName { - continue - } - - // build file scope by processing all imports - importErrors := false - fileScope := NewScope(pkgScope) - for _, spec := range file.Imports { - if importer == nil { - importErrors = true - continue - } - path, _ := strconv.Unquote(spec.Path.Value) - pkg, err := importer(imports, path) - if err != nil { - p.errorf(spec.Path.Pos(), "could not import %s (%s)", path, err) - importErrors = true - continue - } - // TODO(gri) If a local package name != "." is provided, - // global identifier resolution could proceed even if the - // import failed. Consider adjusting the logic here a bit. - - // local name overrides imported package name - name := pkg.Name - if spec.Name != nil { - name = spec.Name.Name - } - - // add import to file scope - if name == "." { - // merge imported scope with file scope - for _, obj := range pkg.Data.(*Scope).Objects { - p.declare(fileScope, pkgScope, obj) - } - } else if name != "_" { - // declare imported package object in file scope - // (do not re-use pkg in the file scope but create - // a new object instead; the Decl field is different - // for different files) - obj := NewObj(Pkg, name) - obj.Decl = spec - obj.Data = pkg.Data - p.declare(fileScope, pkgScope, obj) - } - } - - // resolve identifiers - if importErrors { - // don't use the universe scope without correct imports - // (objects in the universe may be shadowed by imports; - // with missing imports, identifiers might get resolved - // incorrectly to universe objects) - pkgScope.Outer = nil - } - i := 0 - for _, ident := range file.Unresolved { - if !resolve(fileScope, ident) { - p.errorf(ident.Pos(), "undeclared name: %s", ident.Name) - file.Unresolved[i] = ident - i++ - } - - } - file.Unresolved = file.Unresolved[0:i] - pkgScope.Outer = universe // reset universe scope - } - - p.errors.Sort() - return &Package{pkgName, pkgScope, imports, files}, p.errors.Err() -} diff --git a/internal/backport/go/ast/scope.go b/internal/backport/go/ast/scope.go deleted file mode 100644 index 165c9353f9..0000000000 --- a/internal/backport/go/ast/scope.go +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright 2009 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. - -// This file implements scopes and the objects they contain. - -package ast - -import ( - "bytes" - "fmt" - "golang.org/x/website/internal/backport/go/token" -) - -// A Scope maintains the set of named language entities declared -// in the scope and a link to the immediately surrounding (outer) -// scope. -type Scope struct { - Outer *Scope - Objects map[string]*Object -} - -// NewScope creates a new scope nested in the outer scope. -func NewScope(outer *Scope) *Scope { - const n = 4 // initial scope capacity - return &Scope{outer, make(map[string]*Object, n)} -} - -// Lookup returns the object with the given name if it is -// found in scope s, otherwise it returns nil. Outer scopes -// are ignored. -func (s *Scope) Lookup(name string) *Object { - return s.Objects[name] -} - -// Insert attempts to insert a named object obj into the scope s. -// If the scope already contains an object alt with the same name, -// Insert leaves the scope unchanged and returns alt. Otherwise -// it inserts obj and returns nil. -func (s *Scope) Insert(obj *Object) (alt *Object) { - if alt = s.Objects[obj.Name]; alt == nil { - s.Objects[obj.Name] = obj - } - return -} - -// Debugging support -func (s *Scope) String() string { - var buf bytes.Buffer - fmt.Fprintf(&buf, "scope %p {", s) - if s != nil && len(s.Objects) > 0 { - fmt.Fprintln(&buf) - for _, obj := range s.Objects { - fmt.Fprintf(&buf, "\t%s %s\n", obj.Kind, obj.Name) - } - } - fmt.Fprintf(&buf, "}\n") - return buf.String() -} - -// ---------------------------------------------------------------------------- -// Objects - -// An Object describes a named language entity such as a package, -// constant, type, variable, function (incl. methods), or label. -// -// The Data fields contains object-specific data: -// -// Kind Data type Data value -// Pkg *Scope package scope -// Con int iota for the respective declaration -type Object struct { - Kind ObjKind - Name string // declared name - Decl interface{} // corresponding Field, XxxSpec, FuncDecl, LabeledStmt, AssignStmt, Scope; or nil - Data interface{} // object-specific data; or nil - Type interface{} // placeholder for type information; may be nil -} - -// NewObj creates a new object of a given kind and name. -func NewObj(kind ObjKind, name string) *Object { - return &Object{Kind: kind, Name: name} -} - -// Pos computes the source position of the declaration of an object name. -// The result may be an invalid position if it cannot be computed -// (obj.Decl may be nil or not correct). -func (obj *Object) Pos() token.Pos { - name := obj.Name - switch d := obj.Decl.(type) { - case *Field: - for _, n := range d.Names { - if n.Name == name { - return n.Pos() - } - } - case *ImportSpec: - if d.Name != nil && d.Name.Name == name { - return d.Name.Pos() - } - return d.Path.Pos() - case *ValueSpec: - for _, n := range d.Names { - if n.Name == name { - return n.Pos() - } - } - case *TypeSpec: - if d.Name.Name == name { - return d.Name.Pos() - } - case *FuncDecl: - if d.Name.Name == name { - return d.Name.Pos() - } - case *LabeledStmt: - if d.Label.Name == name { - return d.Label.Pos() - } - case *AssignStmt: - for _, x := range d.Lhs { - if ident, isIdent := x.(*Ident); isIdent && ident.Name == name { - return ident.Pos() - } - } - case *Scope: - // predeclared object - nothing to do for now - } - return token.NoPos -} - -// ObjKind describes what an object represents. -type ObjKind int - -// The list of possible Object kinds. -const ( - Bad ObjKind = iota // for error handling - Pkg // package - Con // constant - Typ // type - Var // variable - Fun // function or method - Lbl // label -) - -var objKindStrings = [...]string{ - Bad: "bad", - Pkg: "package", - Con: "const", - Typ: "type", - Var: "var", - Fun: "func", - Lbl: "label", -} - -func (kind ObjKind) String() string { return objKindStrings[kind] } diff --git a/internal/backport/go/ast/walk.go b/internal/backport/go/ast/walk.go deleted file mode 100644 index a293c99a10..0000000000 --- a/internal/backport/go/ast/walk.go +++ /dev/null @@ -1,398 +0,0 @@ -// Copyright 2009 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 ast - -import "fmt" - -// A Visitor's Visit method is invoked for each node encountered by Walk. -// If the result visitor w is not nil, Walk visits each of the children -// of node with the visitor w, followed by a call of w.Visit(nil). -type Visitor interface { - Visit(node Node) (w Visitor) -} - -// Helper functions for common node lists. They may be empty. - -func walkIdentList(v Visitor, list []*Ident) { - for _, x := range list { - Walk(v, x) - } -} - -func walkExprList(v Visitor, list []Expr) { - for _, x := range list { - Walk(v, x) - } -} - -func walkStmtList(v Visitor, list []Stmt) { - for _, x := range list { - Walk(v, x) - } -} - -func walkDeclList(v Visitor, list []Decl) { - for _, x := range list { - Walk(v, x) - } -} - -// TODO(gri): Investigate if providing a closure to Walk leads to -// simpler use (and may help eliminate Inspect in turn). - -// Walk traverses an AST in depth-first order: It starts by calling -// v.Visit(node); node must not be nil. If the visitor w returned by -// v.Visit(node) is not nil, Walk is invoked recursively with visitor -// w for each of the non-nil children of node, followed by a call of -// w.Visit(nil). -func Walk(v Visitor, node Node) { - if v = v.Visit(node); v == nil { - return - } - - // walk children - // (the order of the cases matches the order - // of the corresponding node types in ast.go) - switch n := node.(type) { - // Comments and fields - case *Comment: - // nothing to do - - case *CommentGroup: - for _, c := range n.List { - Walk(v, c) - } - - case *Field: - if n.Doc != nil { - Walk(v, n.Doc) - } - walkIdentList(v, n.Names) - if n.Type != nil { - Walk(v, n.Type) - } - if n.Tag != nil { - Walk(v, n.Tag) - } - if n.Comment != nil { - Walk(v, n.Comment) - } - - case *FieldList: - for _, f := range n.List { - Walk(v, f) - } - - // Expressions - case *BadExpr, *Ident, *BasicLit: - // nothing to do - - case *Ellipsis: - if n.Elt != nil { - Walk(v, n.Elt) - } - - case *FuncLit: - Walk(v, n.Type) - Walk(v, n.Body) - - case *CompositeLit: - if n.Type != nil { - Walk(v, n.Type) - } - walkExprList(v, n.Elts) - - case *ParenExpr: - Walk(v, n.X) - - case *SelectorExpr: - Walk(v, n.X) - Walk(v, n.Sel) - - case *IndexExpr: - Walk(v, n.X) - Walk(v, n.Index) - - case *IndexListExpr: - Walk(v, n.X) - for _, index := range n.Indices { - Walk(v, index) - } - - case *SliceExpr: - Walk(v, n.X) - if n.Low != nil { - Walk(v, n.Low) - } - if n.High != nil { - Walk(v, n.High) - } - if n.Max != nil { - Walk(v, n.Max) - } - - case *TypeAssertExpr: - Walk(v, n.X) - if n.Type != nil { - Walk(v, n.Type) - } - - case *CallExpr: - Walk(v, n.Fun) - walkExprList(v, n.Args) - - case *StarExpr: - Walk(v, n.X) - - case *UnaryExpr: - Walk(v, n.X) - - case *BinaryExpr: - Walk(v, n.X) - Walk(v, n.Y) - - case *KeyValueExpr: - Walk(v, n.Key) - Walk(v, n.Value) - - // Types - case *ArrayType: - if n.Len != nil { - Walk(v, n.Len) - } - Walk(v, n.Elt) - - case *StructType: - Walk(v, n.Fields) - - case *FuncType: - if n.TypeParams != nil { - Walk(v, n.TypeParams) - } - if n.Params != nil { - Walk(v, n.Params) - } - if n.Results != nil { - Walk(v, n.Results) - } - - case *InterfaceType: - Walk(v, n.Methods) - - case *MapType: - Walk(v, n.Key) - Walk(v, n.Value) - - case *ChanType: - Walk(v, n.Value) - - // Statements - case *BadStmt: - // nothing to do - - case *DeclStmt: - Walk(v, n.Decl) - - case *EmptyStmt: - // nothing to do - - case *LabeledStmt: - Walk(v, n.Label) - Walk(v, n.Stmt) - - case *ExprStmt: - Walk(v, n.X) - - case *SendStmt: - Walk(v, n.Chan) - Walk(v, n.Value) - - case *IncDecStmt: - Walk(v, n.X) - - case *AssignStmt: - walkExprList(v, n.Lhs) - walkExprList(v, n.Rhs) - - case *GoStmt: - Walk(v, n.Call) - - case *DeferStmt: - Walk(v, n.Call) - - case *ReturnStmt: - walkExprList(v, n.Results) - - case *BranchStmt: - if n.Label != nil { - Walk(v, n.Label) - } - - case *BlockStmt: - walkStmtList(v, n.List) - - case *IfStmt: - if n.Init != nil { - Walk(v, n.Init) - } - Walk(v, n.Cond) - Walk(v, n.Body) - if n.Else != nil { - Walk(v, n.Else) - } - - case *CaseClause: - walkExprList(v, n.List) - walkStmtList(v, n.Body) - - case *SwitchStmt: - if n.Init != nil { - Walk(v, n.Init) - } - if n.Tag != nil { - Walk(v, n.Tag) - } - Walk(v, n.Body) - - case *TypeSwitchStmt: - if n.Init != nil { - Walk(v, n.Init) - } - Walk(v, n.Assign) - Walk(v, n.Body) - - case *CommClause: - if n.Comm != nil { - Walk(v, n.Comm) - } - walkStmtList(v, n.Body) - - case *SelectStmt: - Walk(v, n.Body) - - case *ForStmt: - if n.Init != nil { - Walk(v, n.Init) - } - if n.Cond != nil { - Walk(v, n.Cond) - } - if n.Post != nil { - Walk(v, n.Post) - } - Walk(v, n.Body) - - case *RangeStmt: - if n.Key != nil { - Walk(v, n.Key) - } - if n.Value != nil { - Walk(v, n.Value) - } - Walk(v, n.X) - Walk(v, n.Body) - - // Declarations - case *ImportSpec: - if n.Doc != nil { - Walk(v, n.Doc) - } - if n.Name != nil { - Walk(v, n.Name) - } - Walk(v, n.Path) - if n.Comment != nil { - Walk(v, n.Comment) - } - - case *ValueSpec: - if n.Doc != nil { - Walk(v, n.Doc) - } - walkIdentList(v, n.Names) - if n.Type != nil { - Walk(v, n.Type) - } - walkExprList(v, n.Values) - if n.Comment != nil { - Walk(v, n.Comment) - } - - case *TypeSpec: - if n.Doc != nil { - Walk(v, n.Doc) - } - Walk(v, n.Name) - if n.TypeParams != nil { - Walk(v, n.TypeParams) - } - Walk(v, n.Type) - if n.Comment != nil { - Walk(v, n.Comment) - } - - case *BadDecl: - // nothing to do - - case *GenDecl: - if n.Doc != nil { - Walk(v, n.Doc) - } - for _, s := range n.Specs { - Walk(v, s) - } - - case *FuncDecl: - if n.Doc != nil { - Walk(v, n.Doc) - } - if n.Recv != nil { - Walk(v, n.Recv) - } - Walk(v, n.Name) - Walk(v, n.Type) - if n.Body != nil { - Walk(v, n.Body) - } - - // Files and packages - case *File: - if n.Doc != nil { - Walk(v, n.Doc) - } - Walk(v, n.Name) - walkDeclList(v, n.Decls) - // don't walk n.Comments - they have been - // visited already through the individual - // nodes - - case *Package: - for _, f := range n.Files { - Walk(v, f) - } - - default: - panic(fmt.Sprintf("ast.Walk: unexpected node type %T", n)) - } - - v.Visit(nil) -} - -type inspector func(Node) bool - -func (f inspector) Visit(node Node) Visitor { - if f(node) { - return f - } - return nil -} - -// Inspect traverses an AST in depth-first order: It starts by calling -// f(node); node must not be nil. If f returns true, Inspect invokes f -// recursively for each of the non-nil children of node, followed by a -// call of f(nil). -func Inspect(node Node, f func(Node) bool) { - Walk(inspector(f), node) -} diff --git a/internal/backport/go/build/build.go b/internal/backport/go/build/build.go deleted file mode 100644 index 01aab3cb8d..0000000000 --- a/internal/backport/go/build/build.go +++ /dev/null @@ -1,1970 +0,0 @@ -// Copyright 2011 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 build - -import ( - "bytes" - "errors" - "fmt" - "go/build" - "go/build/constraint" - "golang.org/x/website/internal/backport/go/doc" - "io" - "io/fs" - "io/ioutil" - "os" - "os/exec" - pathpkg "path" - "path/filepath" - "runtime" - "sort" - "strconv" - "strings" - "unicode" - "unicode/utf8" - - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/token" -) - -// A Context specifies the supporting context for a build. -type Context struct { - GOARCH string // target architecture - GOOS string // target operating system - GOROOT string // Go root - GOPATH string // Go path - - // Dir is the caller's working directory, or the empty string to use - // the current directory of the running process. In module mode, this is used - // to locate the main module. - // - // If Dir is non-empty, directories passed to Import and ImportDir must - // be absolute. - Dir string - - CgoEnabled bool // whether cgo files are included - UseAllFiles bool // use files regardless of +build lines, file names - Compiler string // compiler to assume when computing target paths - - // The build, tool, and release tags specify build constraints - // that should be considered satisfied when processing +build lines. - // Clients creating a new context may customize BuildTags, which - // defaults to empty, but it is usually an error to customize ToolTags or ReleaseTags. - // ToolTags defaults to build tags appropriate to the current Go toolchain configuration. - // ReleaseTags defaults to the list of Go releases the current release is compatible with. - // BuildTags is not set for the Default build Context. - // In addition to the BuildTags, ToolTags, and ReleaseTags, build constraints - // consider the values of GOARCH and GOOS as satisfied tags. - // The last element in ReleaseTags is assumed to be the current release. - BuildTags []string - ToolTags []string - ReleaseTags []string - - // The install suffix specifies a suffix to use in the name of the installation - // directory. By default it is empty, but custom builds that need to keep - // their outputs separate can set InstallSuffix to do so. For example, when - // using the race detector, the go command uses InstallSuffix = "race", so - // that on a Linux/386 system, packages are written to a directory named - // "linux_386_race" instead of the usual "linux_386". - InstallSuffix string - - // By default, Import uses the operating system's file system calls - // to read directories and files. To read from other sources, - // callers can set the following functions. They all have default - // behaviors that use the local file system, so clients need only set - // the functions whose behaviors they wish to change. - - // JoinPath joins the sequence of path fragments into a single path. - // If JoinPath is nil, Import uses filepath.Join. - JoinPath func(elem ...string) string - - // SplitPathList splits the path list into a slice of individual paths. - // If SplitPathList is nil, Import uses filepath.SplitList. - SplitPathList func(list string) []string - - // IsAbsPath reports whether path is an absolute path. - // If IsAbsPath is nil, Import uses filepath.IsAbs. - IsAbsPath func(path string) bool - - // IsDir reports whether the path names a directory. - // If IsDir is nil, Import calls os.Stat and uses the result's IsDir method. - IsDir func(path string) bool - - // HasSubdir reports whether dir is lexically a subdirectory of - // root, perhaps multiple levels below. It does not try to check - // whether dir exists. - // If so, HasSubdir sets rel to a slash-separated path that - // can be joined to root to produce a path equivalent to dir. - // If HasSubdir is nil, Import uses an implementation built on - // filepath.EvalSymlinks. - HasSubdir func(root, dir string) (rel string, ok bool) - - // ReadDir returns a slice of fs.FileInfo, sorted by Name, - // describing the content of the named directory. - // If ReadDir is nil, Import uses ioutil.ReadDir. - ReadDir func(dir string) ([]fs.FileInfo, error) - - // OpenFile opens a file (not a directory) for reading. - // If OpenFile is nil, Import uses os.Open. - OpenFile func(path string) (io.ReadCloser, error) -} - -// joinPath calls ctxt.JoinPath (if not nil) or else filepath.Join. -func (ctxt *Context) joinPath(elem ...string) string { - if f := ctxt.JoinPath; f != nil { - return f(elem...) - } - return filepath.Join(elem...) -} - -// splitPathList calls ctxt.SplitPathList (if not nil) or else filepath.SplitList. -func (ctxt *Context) splitPathList(s string) []string { - if f := ctxt.SplitPathList; f != nil { - return f(s) - } - return filepath.SplitList(s) -} - -// isAbsPath calls ctxt.IsAbsPath (if not nil) or else filepath.IsAbs. -func (ctxt *Context) isAbsPath(path string) bool { - if f := ctxt.IsAbsPath; f != nil { - return f(path) - } - return filepath.IsAbs(path) -} - -// isDir calls ctxt.IsDir (if not nil) or else uses os.Stat. -func (ctxt *Context) isDir(path string) bool { - if f := ctxt.IsDir; f != nil { - return f(path) - } - fi, err := os.Stat(path) - return err == nil && fi.IsDir() -} - -// hasSubdir calls ctxt.HasSubdir (if not nil) or else uses -// the local file system to answer the question. -func (ctxt *Context) hasSubdir(root, dir string) (rel string, ok bool) { - if f := ctxt.HasSubdir; f != nil { - return f(root, dir) - } - - // Try using paths we received. - if rel, ok = hasSubdir(root, dir); ok { - return - } - - // Try expanding symlinks and comparing - // expanded against unexpanded and - // expanded against expanded. - rootSym, _ := filepath.EvalSymlinks(root) - dirSym, _ := filepath.EvalSymlinks(dir) - - if rel, ok = hasSubdir(rootSym, dir); ok { - return - } - if rel, ok = hasSubdir(root, dirSym); ok { - return - } - return hasSubdir(rootSym, dirSym) -} - -// hasSubdir reports if dir is within root by performing lexical analysis only. -func hasSubdir(root, dir string) (rel string, ok bool) { - const sep = string(filepath.Separator) - root = filepath.Clean(root) - if !strings.HasSuffix(root, sep) { - root += sep - } - dir = filepath.Clean(dir) - if !strings.HasPrefix(dir, root) { - return "", false - } - return filepath.ToSlash(dir[len(root):]), true -} - -// readDir calls ctxt.ReadDir (if not nil) or else ioutil.ReadDir. -func (ctxt *Context) readDir(path string) ([]fs.FileInfo, error) { - if f := ctxt.ReadDir; f != nil { - return f(path) - } - // TODO: use os.ReadDir - return ioutil.ReadDir(path) -} - -// openFile calls ctxt.OpenFile (if not nil) or else os.Open. -func (ctxt *Context) openFile(path string) (io.ReadCloser, error) { - if fn := ctxt.OpenFile; fn != nil { - return fn(path) - } - - f, err := os.Open(path) - if err != nil { - return nil, err // nil interface - } - return f, nil -} - -// isFile determines whether path is a file by trying to open it. -// It reuses openFile instead of adding another function to the -// list in Context. -func (ctxt *Context) isFile(path string) bool { - f, err := ctxt.openFile(path) - if err != nil { - return false - } - f.Close() - return true -} - -// gopath returns the list of Go path directories. -func (ctxt *Context) gopath() []string { - var all []string - for _, p := range ctxt.splitPathList(ctxt.GOPATH) { - if p == "" || p == ctxt.GOROOT { - // Empty paths are uninteresting. - // If the path is the GOROOT, ignore it. - // People sometimes set GOPATH=$GOROOT. - // Do not get confused by this common mistake. - continue - } - if strings.HasPrefix(p, "~") { - // Path segments starting with ~ on Unix are almost always - // users who have incorrectly quoted ~ while setting GOPATH, - // preventing it from expanding to $HOME. - // The situation is made more confusing by the fact that - // bash allows quoted ~ in $PATH (most shells do not). - // Do not get confused by this, and do not try to use the path. - // It does not exist, and printing errors about it confuses - // those users even more, because they think "sure ~ exists!". - // The go command diagnoses this situation and prints a - // useful error. - // On Windows, ~ is used in short names, such as c:\progra~1 - // for c:\program files. - continue - } - all = append(all, p) - } - return all -} - -// SrcDirs returns a list of package source root directories. -// It draws from the current Go root and Go path but omits directories -// that do not exist. -func (ctxt *Context) SrcDirs() []string { - var all []string - if ctxt.GOROOT != "" && ctxt.Compiler != "gccgo" { - dir := ctxt.joinPath(ctxt.GOROOT, "src") - if ctxt.isDir(dir) { - all = append(all, dir) - } - } - for _, p := range ctxt.gopath() { - dir := ctxt.joinPath(p, "src") - if ctxt.isDir(dir) { - all = append(all, dir) - } - } - return all -} - -// Default is the default Context for builds. -// It uses the GOARCH, GOOS, GOROOT, and GOPATH environment variables -// if set, or else the compiled code's GOARCH, GOOS, and GOROOT. -var Default Context = defaultContext() - -func defaultContext() Context { - d := build.Default - return Context{ - GOARCH: d.GOARCH, - GOOS: d.GOOS, - GOROOT: d.GOROOT, - GOPATH: d.GOPATH, - Dir: d.Dir, - CgoEnabled: d.CgoEnabled, - UseAllFiles: d.UseAllFiles, - Compiler: d.Compiler, - BuildTags: d.BuildTags, - ReleaseTags: d.ReleaseTags, - InstallSuffix: d.InstallSuffix, - JoinPath: d.JoinPath, - SplitPathList: d.SplitPathList, - IsAbsPath: d.IsAbsPath, - IsDir: d.IsDir, - HasSubdir: d.HasSubdir, - ReadDir: d.ReadDir, - OpenFile: d.OpenFile, - } -} - -func defaultGOPATH() string { - env := "HOME" - if runtime.GOOS == "windows" { - env = "USERPROFILE" - } else if runtime.GOOS == "plan9" { - env = "home" - } - if home := os.Getenv(env); home != "" { - def := filepath.Join(home, "go") - if filepath.Clean(def) == filepath.Clean(runtime.GOROOT()) { - // Don't set the default GOPATH to GOROOT, - // as that will trigger warnings from the go tool. - return "" - } - return def - } - return "" -} - -var defaultReleaseTags = build.Default.ReleaseTags - -func envOr(name, def string) string { - s := os.Getenv(name) - if s == "" { - return def - } - return s -} - -// An ImportMode controls the behavior of the Import method. -type ImportMode uint - -const ( - // If FindOnly is set, Import stops after locating the directory - // that should contain the sources for a package. It does not - // read any files in the directory. - FindOnly ImportMode = 1 << iota - - // If AllowBinary is set, Import can be satisfied by a compiled - // package object without corresponding sources. - // - // Deprecated: - // The supported way to create a compiled-only package is to - // write source code containing a //go:binary-only-package comment at - // the top of the file. Such a package will be recognized - // regardless of this flag setting (because it has source code) - // and will have BinaryOnly set to true in the returned Package. - AllowBinary - - // If ImportComment is set, parse import comments on package statements. - // Import returns an error if it finds a comment it cannot understand - // or finds conflicting comments in multiple source files. - // See golang.org/s/go14customimport for more information. - ImportComment - - // By default, Import searches vendor directories - // that apply in the given source directory before searching - // the GOROOT and GOPATH roots. - // If an Import finds and returns a package using a vendor - // directory, the resulting ImportPath is the complete path - // to the package, including the path elements leading up - // to and including "vendor". - // For example, if Import("y", "x/subdir", 0) finds - // "x/vendor/y", the returned package's ImportPath is "x/vendor/y", - // not plain "y". - // See golang.org/s/go15vendor for more information. - // - // Setting IgnoreVendor ignores vendor directories. - // - // In contrast to the package's ImportPath, - // the returned package's Imports, TestImports, and XTestImports - // are always the exact import paths from the source files: - // Import makes no attempt to resolve or check those paths. - IgnoreVendor -) - -// A Package describes the Go package found in a directory. -type Package struct { - Dir string // directory containing package sources - Name string // package name - ImportComment string // path in import comment on package statement - Doc string // documentation synopsis - ImportPath string // import path of package ("" if unknown) - Root string // root of Go tree where this package lives - SrcRoot string // package source root directory ("" if unknown) - PkgRoot string // package install root directory ("" if unknown) - PkgTargetRoot string // architecture dependent install root directory ("" if unknown) - BinDir string // command install directory ("" if unknown) - Goroot bool // package found in Go root - PkgObj string // installed .a file - AllTags []string // tags that can influence file selection in this directory - ConflictDir string // this directory shadows Dir in $GOPATH - BinaryOnly bool // cannot be rebuilt from source (has //go:binary-only-package comment) - - // Source files - GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles) - CgoFiles []string // .go source files that import "C" - IgnoredGoFiles []string // .go source files ignored for this build (including ignored _test.go files) - InvalidGoFiles []string // .go source files with detected problems (parse error, wrong package name, and so on) - IgnoredOtherFiles []string // non-.go source files ignored for this build - CFiles []string // .c source files - CXXFiles []string // .cc, .cpp and .cxx source files - MFiles []string // .m (Objective-C) source files - HFiles []string // .h, .hh, .hpp and .hxx source files - FFiles []string // .f, .F, .for and .f90 Fortran source files - SFiles []string // .s source files - SwigFiles []string // .swig files - SwigCXXFiles []string // .swigcxx files - SysoFiles []string // .syso system object files to add to archive - - // Cgo directives - CgoCFLAGS []string // Cgo CFLAGS directives - CgoCPPFLAGS []string // Cgo CPPFLAGS directives - CgoCXXFLAGS []string // Cgo CXXFLAGS directives - CgoFFLAGS []string // Cgo FFLAGS directives - CgoLDFLAGS []string // Cgo LDFLAGS directives - CgoPkgConfig []string // Cgo pkg-config directives - - // Test information - TestGoFiles []string // _test.go files in package - XTestGoFiles []string // _test.go files outside package - - // Dependency information - Imports []string // import paths from GoFiles, CgoFiles - ImportPos map[string][]token.Position // line information for Imports - TestImports []string // import paths from TestGoFiles - TestImportPos map[string][]token.Position // line information for TestImports - XTestImports []string // import paths from XTestGoFiles - XTestImportPos map[string][]token.Position // line information for XTestImports - - // //go:embed patterns found in Go source files - // For example, if a source file says - // //go:embed a* b.c - // then the list will contain those two strings as separate entries. - // (See package embed for more details about //go:embed.) - EmbedPatterns []string // patterns from GoFiles, CgoFiles - EmbedPatternPos map[string][]token.Position // line information for EmbedPatterns - TestEmbedPatterns []string // patterns from TestGoFiles - TestEmbedPatternPos map[string][]token.Position // line information for TestEmbedPatterns - XTestEmbedPatterns []string // patterns from XTestGoFiles - XTestEmbedPatternPos map[string][]token.Position // line information for XTestEmbedPatternPos -} - -// IsCommand reports whether the package is considered a -// command to be installed (not just a library). -// Packages named "main" are treated as commands. -func (p *Package) IsCommand() bool { - return p.Name == "main" -} - -// ImportDir is like Import but processes the Go package found in -// the named directory. -func (ctxt *Context) ImportDir(dir string, mode ImportMode) (*Package, error) { - return ctxt.Import(".", dir, mode) -} - -// NoGoError is the error used by Import to describe a directory -// containing no buildable Go source files. (It may still contain -// test files, files hidden by build tags, and so on.) -type NoGoError struct { - Dir string -} - -func (e *NoGoError) Error() string { - return "no buildable Go source files in " + e.Dir -} - -// MultiplePackageError describes a directory containing -// multiple buildable Go source files for multiple packages. -type MultiplePackageError struct { - Dir string // directory containing files - Packages []string // package names found - Files []string // corresponding files: Files[i] declares package Packages[i] -} - -func (e *MultiplePackageError) Error() string { - // Error string limited to two entries for compatibility. - return fmt.Sprintf("found packages %s (%s) and %s (%s) in %s", e.Packages[0], e.Files[0], e.Packages[1], e.Files[1], e.Dir) -} - -func nameExt(name string) string { - i := strings.LastIndex(name, ".") - if i < 0 { - return "" - } - return name[i:] -} - -// Import returns details about the Go package named by the import path, -// interpreting local import paths relative to the srcDir directory. -// If the path is a local import path naming a package that can be imported -// using a standard import path, the returned package will set p.ImportPath -// to that path. -// -// In the directory containing the package, .go, .c, .h, and .s files are -// considered part of the package except for: -// -// - .go files in package documentation -// - files starting with _ or . (likely editor temporary files) -// - files with build constraints not satisfied by the context -// -// If an error occurs, Import returns a non-nil error and a non-nil -// *Package containing partial information. -func (ctxt *Context) Import(path string, srcDir string, mode ImportMode) (*Package, error) { - p := &Package{ - ImportPath: path, - } - if path == "" { - return p, fmt.Errorf("import %q: invalid import path", path) - } - - var pkgtargetroot string - var pkga string - var pkgerr error - suffix := "" - if ctxt.InstallSuffix != "" { - suffix = "_" + ctxt.InstallSuffix - } - switch ctxt.Compiler { - case "gccgo": - pkgtargetroot = "pkg/gccgo_" + ctxt.GOOS + "_" + ctxt.GOARCH + suffix - case "gc": - pkgtargetroot = "pkg/" + ctxt.GOOS + "_" + ctxt.GOARCH + suffix - default: - // Save error for end of function. - pkgerr = fmt.Errorf("import %q: unknown compiler %q", path, ctxt.Compiler) - } - setPkga := func() { - switch ctxt.Compiler { - case "gccgo": - dir, elem := pathpkg.Split(p.ImportPath) - pkga = pkgtargetroot + "/" + dir + "lib" + elem + ".a" - case "gc": - pkga = pkgtargetroot + "/" + p.ImportPath + ".a" - } - } - setPkga() - - binaryOnly := false - if IsLocalImport(path) { - pkga = "" // local imports have no installed path - if srcDir == "" { - return p, fmt.Errorf("import %q: import relative to unknown directory", path) - } - if !ctxt.isAbsPath(path) { - p.Dir = ctxt.joinPath(srcDir, path) - } - // p.Dir directory may or may not exist. Gather partial information first, check if it exists later. - // Determine canonical import path, if any. - // Exclude results where the import path would include /testdata/. - inTestdata := func(sub string) bool { - return strings.Contains(sub, "/testdata/") || strings.HasSuffix(sub, "/testdata") || strings.HasPrefix(sub, "testdata/") || sub == "testdata" - } - if ctxt.GOROOT != "" { - root := ctxt.joinPath(ctxt.GOROOT, "src") - if sub, ok := ctxt.hasSubdir(root, p.Dir); ok && !inTestdata(sub) { - p.Goroot = true - p.ImportPath = sub - p.Root = ctxt.GOROOT - setPkga() // p.ImportPath changed - goto Found - } - } - all := ctxt.gopath() - for i, root := range all { - rootsrc := ctxt.joinPath(root, "src") - if sub, ok := ctxt.hasSubdir(rootsrc, p.Dir); ok && !inTestdata(sub) { - // We found a potential import path for dir, - // but check that using it wouldn't find something - // else first. - if ctxt.GOROOT != "" && ctxt.Compiler != "gccgo" { - if dir := ctxt.joinPath(ctxt.GOROOT, "src", sub); ctxt.isDir(dir) { - p.ConflictDir = dir - goto Found - } - } - for _, earlyRoot := range all[:i] { - if dir := ctxt.joinPath(earlyRoot, "src", sub); ctxt.isDir(dir) { - p.ConflictDir = dir - goto Found - } - } - - // sub would not name some other directory instead of this one. - // Record it. - p.ImportPath = sub - p.Root = root - setPkga() // p.ImportPath changed - goto Found - } - } - // It's okay that we didn't find a root containing dir. - // Keep going with the information we have. - } else { - if strings.HasPrefix(path, "/") { - return p, fmt.Errorf("import %q: cannot import absolute path", path) - } - - if err := ctxt.importGo(p, path, srcDir, mode); err == nil { - goto Found - } else if err != errNoModules { - return p, err - } - - gopath := ctxt.gopath() // needed twice below; avoid computing many times - - // tried records the location of unsuccessful package lookups - var tried struct { - vendor []string - goroot string - gopath []string - } - - // Vendor directories get first chance to satisfy import. - if mode&IgnoreVendor == 0 && srcDir != "" { - searchVendor := func(root string, isGoroot bool) bool { - sub, ok := ctxt.hasSubdir(root, srcDir) - if !ok || !strings.HasPrefix(sub, "src/") || strings.Contains(sub, "/testdata/") { - return false - } - for { - vendor := ctxt.joinPath(root, sub, "vendor") - if ctxt.isDir(vendor) { - dir := ctxt.joinPath(vendor, path) - if ctxt.isDir(dir) && hasGoFiles(ctxt, dir) { - p.Dir = dir - p.ImportPath = strings.TrimPrefix(pathpkg.Join(sub, "vendor", path), "src/") - p.Goroot = isGoroot - p.Root = root - setPkga() // p.ImportPath changed - return true - } - tried.vendor = append(tried.vendor, dir) - } - i := strings.LastIndex(sub, "/") - if i < 0 { - break - } - sub = sub[:i] - } - return false - } - if ctxt.Compiler != "gccgo" && searchVendor(ctxt.GOROOT, true) { - goto Found - } - for _, root := range gopath { - if searchVendor(root, false) { - goto Found - } - } - } - - // Determine directory from import path. - if ctxt.GOROOT != "" { - // If the package path starts with "vendor/", only search GOROOT before - // GOPATH if the importer is also within GOROOT. That way, if the user has - // vendored in a package that is subsequently included in the standard - // distribution, they'll continue to pick up their own vendored copy. - gorootFirst := srcDir == "" || !strings.HasPrefix(path, "vendor/") - if !gorootFirst { - _, gorootFirst = ctxt.hasSubdir(ctxt.GOROOT, srcDir) - } - if gorootFirst { - dir := ctxt.joinPath(ctxt.GOROOT, "src", path) - if ctxt.Compiler != "gccgo" { - isDir := ctxt.isDir(dir) - binaryOnly = !isDir && mode&AllowBinary != 0 && pkga != "" && ctxt.isFile(ctxt.joinPath(ctxt.GOROOT, pkga)) - if isDir || binaryOnly { - p.Dir = dir - p.Goroot = true - p.Root = ctxt.GOROOT - goto Found - } - } - tried.goroot = dir - } - } - for _, root := range gopath { - dir := ctxt.joinPath(root, "src", path) - isDir := ctxt.isDir(dir) - binaryOnly = !isDir && mode&AllowBinary != 0 && pkga != "" && ctxt.isFile(ctxt.joinPath(root, pkga)) - if isDir || binaryOnly { - p.Dir = dir - p.Root = root - goto Found - } - tried.gopath = append(tried.gopath, dir) - } - - // If we tried GOPATH first due to a "vendor/" prefix, fall back to GOPATH. - // That way, the user can still get useful results from 'go list' for - // standard-vendored paths passed on the command line. - if ctxt.GOROOT != "" && tried.goroot == "" { - dir := ctxt.joinPath(ctxt.GOROOT, "src", path) - if ctxt.Compiler != "gccgo" { - isDir := ctxt.isDir(dir) - binaryOnly = !isDir && mode&AllowBinary != 0 && pkga != "" && ctxt.isFile(ctxt.joinPath(ctxt.GOROOT, pkga)) - if isDir || binaryOnly { - p.Dir = dir - p.Goroot = true - p.Root = ctxt.GOROOT - goto Found - } - } - tried.goroot = dir - } - - // package was not found - var paths []string - format := "\t%s (vendor tree)" - for _, dir := range tried.vendor { - paths = append(paths, fmt.Sprintf(format, dir)) - format = "\t%s" - } - if tried.goroot != "" { - paths = append(paths, fmt.Sprintf("\t%s (from $GOROOT)", tried.goroot)) - } else { - paths = append(paths, "\t($GOROOT not set)") - } - format = "\t%s (from $GOPATH)" - for _, dir := range tried.gopath { - paths = append(paths, fmt.Sprintf(format, dir)) - format = "\t%s" - } - if len(tried.gopath) == 0 { - paths = append(paths, "\t($GOPATH not set. For more details see: 'go help gopath')") - } - return p, fmt.Errorf("cannot find package %q in any of:\n%s", path, strings.Join(paths, "\n")) - } - -Found: - if p.Root != "" { - p.SrcRoot = ctxt.joinPath(p.Root, "src") - p.PkgRoot = ctxt.joinPath(p.Root, "pkg") - p.BinDir = ctxt.joinPath(p.Root, "bin") - if pkga != "" { - p.PkgTargetRoot = ctxt.joinPath(p.Root, pkgtargetroot) - p.PkgObj = ctxt.joinPath(p.Root, pkga) - } - } - - // If it's a local import path, by the time we get here, we still haven't checked - // that p.Dir directory exists. This is the right time to do that check. - // We can't do it earlier, because we want to gather partial information for the - // non-nil *Package returned when an error occurs. - // We need to do this before we return early on FindOnly flag. - if IsLocalImport(path) && !ctxt.isDir(p.Dir) { - if ctxt.Compiler == "gccgo" && p.Goroot { - // gccgo has no sources for GOROOT packages. - return p, nil - } - - // package was not found - return p, fmt.Errorf("cannot find package %q in:\n\t%s", p.ImportPath, p.Dir) - } - - if mode&FindOnly != 0 { - return p, pkgerr - } - if binaryOnly && (mode&AllowBinary) != 0 { - return p, pkgerr - } - - if ctxt.Compiler == "gccgo" && p.Goroot { - // gccgo has no sources for GOROOT packages. - return p, nil - } - - dirs, err := ctxt.readDir(p.Dir) - if err != nil { - return p, err - } - - var badGoError error - badFiles := make(map[string]bool) - badFile := func(name string, err error) { - if badGoError == nil { - badGoError = err - } - if !badFiles[name] { - p.InvalidGoFiles = append(p.InvalidGoFiles, name) - badFiles[name] = true - } - } - - var Sfiles []string // files with ".S"(capital S)/.sx(capital s equivalent for case insensitive filesystems) - var firstFile, firstCommentFile string - embedPos := make(map[string][]token.Position) - testEmbedPos := make(map[string][]token.Position) - xTestEmbedPos := make(map[string][]token.Position) - importPos := make(map[string][]token.Position) - testImportPos := make(map[string][]token.Position) - xTestImportPos := make(map[string][]token.Position) - allTags := make(map[string]bool) - fset := token.NewFileSet() - for _, d := range dirs { - if d.IsDir() { - continue - } - if d.Mode()&fs.ModeSymlink != 0 { - if ctxt.isDir(ctxt.joinPath(p.Dir, d.Name())) { - // Symlinks to directories are not source files. - continue - } - } - - name := d.Name() - ext := nameExt(name) - - info, err := ctxt.matchFile(p.Dir, name, allTags, &p.BinaryOnly, fset) - if err != nil { - badFile(name, err) - continue - } - if info == nil { - if strings.HasPrefix(name, "_") || strings.HasPrefix(name, ".") { - // not due to build constraints - don't report - } else if ext == ".go" { - p.IgnoredGoFiles = append(p.IgnoredGoFiles, name) - } else if fileListForExt(p, ext) != nil { - p.IgnoredOtherFiles = append(p.IgnoredOtherFiles, name) - } - continue - } - data, filename := info.header, info.name - - // Going to save the file. For non-Go files, can stop here. - switch ext { - case ".go": - // keep going - case ".S", ".sx": - // special case for cgo, handled at end - Sfiles = append(Sfiles, name) - continue - default: - if list := fileListForExt(p, ext); list != nil { - *list = append(*list, name) - } - continue - } - - if info.parseErr != nil { - badFile(name, info.parseErr) - // Fall through: we might still have a partial AST in info.parsed, - // and we want to list files with parse errors anyway. - } - - var pkg string - if info.parsed != nil { - pkg = info.parsed.Name.Name - if pkg == "documentation" { - p.IgnoredGoFiles = append(p.IgnoredGoFiles, name) - continue - } - } - - isTest := strings.HasSuffix(name, "_test.go") - isXTest := false - if isTest && strings.HasSuffix(pkg, "_test") && p.Name != pkg { - isXTest = true - pkg = pkg[:len(pkg)-len("_test")] - } - - if p.Name == "" { - p.Name = pkg - firstFile = name - } else if pkg != p.Name { - // TODO(#45999): The choice of p.Name is arbitrary based on file iteration - // order. Instead of resolving p.Name arbitrarily, we should clear out the - // existing name and mark the existing files as also invalid. - badFile(name, &MultiplePackageError{ - Dir: p.Dir, - Packages: []string{p.Name, pkg}, - Files: []string{firstFile, name}, - }) - } - // Grab the first package comment as docs, provided it is not from a test file. - if info.parsed != nil && info.parsed.Doc != nil && p.Doc == "" && !isTest && !isXTest { - p.Doc = doc.Synopsis(info.parsed.Doc.Text()) - } - - if mode&ImportComment != 0 { - qcom, line := findImportComment(data) - if line != 0 { - com, err := strconv.Unquote(qcom) - if err != nil { - badFile(name, fmt.Errorf("%s:%d: cannot parse import comment", filename, line)) - } else if p.ImportComment == "" { - p.ImportComment = com - firstCommentFile = name - } else if p.ImportComment != com { - badFile(name, fmt.Errorf("found import comments %q (%s) and %q (%s) in %s", p.ImportComment, firstCommentFile, com, name, p.Dir)) - } - } - } - - // Record imports and information about cgo. - isCgo := false - for _, imp := range info.imports { - if imp.path == "C" { - if isTest { - badFile(name, fmt.Errorf("use of cgo in test %s not supported", filename)) - continue - } - isCgo = true - if imp.doc != nil { - if err := ctxt.saveCgo(filename, p, imp.doc); err != nil { - badFile(name, err) - } - } - } - } - - var fileList *[]string - var importMap, embedMap map[string][]token.Position - switch { - case isCgo: - allTags["cgo"] = true - if ctxt.CgoEnabled { - fileList = &p.CgoFiles - importMap = importPos - embedMap = embedPos - } else { - // Ignore imports and embeds from cgo files if cgo is disabled. - fileList = &p.IgnoredGoFiles - } - case isXTest: - fileList = &p.XTestGoFiles - importMap = xTestImportPos - embedMap = xTestEmbedPos - case isTest: - fileList = &p.TestGoFiles - importMap = testImportPos - embedMap = testEmbedPos - default: - fileList = &p.GoFiles - importMap = importPos - embedMap = embedPos - } - *fileList = append(*fileList, name) - if importMap != nil { - for _, imp := range info.imports { - importMap[imp.path] = append(importMap[imp.path], fset.Position(imp.pos)) - } - } - if embedMap != nil { - for _, emb := range info.embeds { - embedMap[emb.pattern] = append(embedMap[emb.pattern], emb.pos) - } - } - } - - for tag := range allTags { - p.AllTags = append(p.AllTags, tag) - } - sort.Strings(p.AllTags) - - p.EmbedPatterns, p.EmbedPatternPos = cleanDecls(embedPos) - p.TestEmbedPatterns, p.TestEmbedPatternPos = cleanDecls(testEmbedPos) - p.XTestEmbedPatterns, p.XTestEmbedPatternPos = cleanDecls(xTestEmbedPos) - - p.Imports, p.ImportPos = cleanDecls(importPos) - p.TestImports, p.TestImportPos = cleanDecls(testImportPos) - p.XTestImports, p.XTestImportPos = cleanDecls(xTestImportPos) - - // add the .S/.sx files only if we are using cgo - // (which means gcc will compile them). - // The standard assemblers expect .s files. - if len(p.CgoFiles) > 0 { - p.SFiles = append(p.SFiles, Sfiles...) - sort.Strings(p.SFiles) - } else { - p.IgnoredOtherFiles = append(p.IgnoredOtherFiles, Sfiles...) - sort.Strings(p.IgnoredOtherFiles) - } - - if badGoError != nil { - return p, badGoError - } - if len(p.GoFiles)+len(p.CgoFiles)+len(p.TestGoFiles)+len(p.XTestGoFiles) == 0 { - return p, &NoGoError{p.Dir} - } - return p, pkgerr -} - -func fileListForExt(p *Package, ext string) *[]string { - switch ext { - case ".c": - return &p.CFiles - case ".cc", ".cpp", ".cxx": - return &p.CXXFiles - case ".m": - return &p.MFiles - case ".h", ".hh", ".hpp", ".hxx": - return &p.HFiles - case ".f", ".F", ".for", ".f90": - return &p.FFiles - case ".s", ".S", ".sx": - return &p.SFiles - case ".swig": - return &p.SwigFiles - case ".swigcxx": - return &p.SwigCXXFiles - case ".syso": - return &p.SysoFiles - } - return nil -} - -func uniq(list []string) []string { - if list == nil { - return nil - } - out := make([]string, len(list)) - copy(out, list) - sort.Strings(out) - uniq := out[:0] - for _, x := range out { - if len(uniq) == 0 || uniq[len(uniq)-1] != x { - uniq = append(uniq, x) - } - } - return uniq -} - -var errNoModules = errors.New("not using modules") - -// importGo checks whether it can use the go command to find the directory for path. -// If using the go command is not appropriate, importGo returns errNoModules. -// Otherwise, importGo tries using the go command and reports whether that succeeded. -// Using the go command lets build.Import and build.Context.Import find code -// in Go modules. In the long term we want tools to use go/packages (currently golang.org/x/tools/go/packages), -// which will also use the go command. -// Invoking the go command here is not very efficient in that it computes information -// about the requested package and all dependencies and then only reports about the requested package. -// Then we reinvoke it for every dependency. But this is still better than not working at all. -// See golang.org/issue/26504. -func (ctxt *Context) importGo(p *Package, path, srcDir string, mode ImportMode) error { - // To invoke the go command, - // we must not being doing special things like AllowBinary or IgnoreVendor, - // and all the file system callbacks must be nil (we're meant to use the local file system). - if mode&AllowBinary != 0 || mode&IgnoreVendor != 0 || - ctxt.JoinPath != nil || ctxt.SplitPathList != nil || ctxt.IsAbsPath != nil || ctxt.IsDir != nil || ctxt.HasSubdir != nil || ctxt.ReadDir != nil || ctxt.OpenFile != nil || !equal(ctxt.ReleaseTags, defaultReleaseTags) { - return errNoModules - } - - // Predict whether module aware mode is enabled by checking the value of - // GO111MODULE and looking for a go.mod file in the source directory or - // one of its parents. Running 'go env GOMOD' in the source directory would - // give a canonical answer, but we'd prefer not to execute another command. - go111Module := os.Getenv("GO111MODULE") - switch go111Module { - case "off": - return errNoModules - default: // "", "on", "auto", anything else - // Maybe use modules. - } - - if srcDir != "" { - var absSrcDir string - if filepath.IsAbs(srcDir) { - absSrcDir = srcDir - } else if ctxt.Dir != "" { - return fmt.Errorf("go/build: Dir is non-empty, so relative srcDir is not allowed: %v", srcDir) - } else { - // Find the absolute source directory. hasSubdir does not handle - // relative paths (and can't because the callbacks don't support this). - var err error - absSrcDir, err = filepath.Abs(srcDir) - if err != nil { - return errNoModules - } - } - - // If the source directory is in GOROOT, then the in-process code works fine - // and we should keep using it. Moreover, the 'go list' approach below doesn't - // take standard-library vendoring into account and will fail. - if _, ok := ctxt.hasSubdir(filepath.Join(ctxt.GOROOT, "src"), absSrcDir); ok { - return errNoModules - } - } - - // For efficiency, if path is a standard library package, let the usual lookup code handle it. - if ctxt.GOROOT != "" { - dir := ctxt.joinPath(ctxt.GOROOT, "src", path) - if ctxt.isDir(dir) { - return errNoModules - } - } - - // If GO111MODULE=auto, look to see if there is a go.mod. - // Since go1.13, it doesn't matter if we're inside GOPATH. - if go111Module == "auto" { - var ( - parent string - err error - ) - if ctxt.Dir == "" { - parent, err = os.Getwd() - if err != nil { - // A nonexistent working directory can't be in a module. - return errNoModules - } - } else { - parent, err = filepath.Abs(ctxt.Dir) - if err != nil { - // If the caller passed a bogus Dir explicitly, that's materially - // different from not having modules enabled. - return err - } - } - for { - if f, err := ctxt.openFile(ctxt.joinPath(parent, "go.mod")); err == nil { - buf := make([]byte, 100) - _, err := f.Read(buf) - f.Close() - if err == nil || err == io.EOF { - // go.mod exists and is readable (is a file, not a directory). - break - } - } - d := filepath.Dir(parent) - if len(d) >= len(parent) { - return errNoModules // reached top of file system, no go.mod - } - parent = d - } - } - - cmd := exec.Command("go", "list", "-e", "-compiler="+ctxt.Compiler, "-tags="+strings.Join(ctxt.BuildTags, ","), "-installsuffix="+ctxt.InstallSuffix, "-f={{.Dir}}\n{{.ImportPath}}\n{{.Root}}\n{{.Goroot}}\n{{if .Error}}{{.Error}}{{end}}\n", "--", path) - - if ctxt.Dir != "" { - cmd.Dir = ctxt.Dir - } - - var stdout, stderr strings.Builder - cmd.Stdout = &stdout - cmd.Stderr = &stderr - - cgo := "0" - if ctxt.CgoEnabled { - cgo = "1" - } - cmd.Env = append(os.Environ(), - "GOOS="+ctxt.GOOS, - "GOARCH="+ctxt.GOARCH, - "GOROOT="+ctxt.GOROOT, - "GOPATH="+ctxt.GOPATH, - "CGO_ENABLED="+cgo, - ) - - if err := cmd.Run(); err != nil { - return fmt.Errorf("go/build: go list %s: %v\n%s\n", path, err, stderr.String()) - } - - f := strings.SplitN(stdout.String(), "\n", 5) - if len(f) != 5 { - return fmt.Errorf("go/build: importGo %s: unexpected output:\n%s\n", path, stdout.String()) - } - dir := f[0] - errStr := strings.TrimSpace(f[4]) - if errStr != "" && dir == "" { - // If 'go list' could not locate the package (dir is empty), - // return the same error that 'go list' reported. - return errors.New(errStr) - } - - // If 'go list' did locate the package, ignore the error. - // It was probably related to loading source files, and we'll - // encounter it ourselves shortly if the FindOnly flag isn't set. - p.Dir = dir - p.ImportPath = f[1] - p.Root = f[2] - p.Goroot = f[3] == "true" - return nil -} - -func equal(x, y []string) bool { - if len(x) != len(y) { - return false - } - for i, xi := range x { - if xi != y[i] { - return false - } - } - return true -} - -// hasGoFiles reports whether dir contains any files with names ending in .go. -// For a vendor check we must exclude directories that contain no .go files. -// Otherwise it is not possible to vendor just a/b/c and still import the -// non-vendored a/b. See golang.org/issue/13832. -func hasGoFiles(ctxt *Context, dir string) bool { - ents, _ := ctxt.readDir(dir) - for _, ent := range ents { - if !ent.IsDir() && strings.HasSuffix(ent.Name(), ".go") { - return true - } - } - return false -} - -func findImportComment(data []byte) (s string, line int) { - // expect keyword package - word, data := parseWord(data) - if string(word) != "package" { - return "", 0 - } - - // expect package name - _, data = parseWord(data) - - // now ready for import comment, a // or /* */ comment - // beginning and ending on the current line. - for len(data) > 0 && (data[0] == ' ' || data[0] == '\t' || data[0] == '\r') { - data = data[1:] - } - - var comment []byte - switch { - case bytes.HasPrefix(data, slashSlash): - comment, _, _ = bytesCut(data[2:], newline) - case bytes.HasPrefix(data, slashStar): - var ok bool - comment, _, ok = bytesCut(data[2:], starSlash) - if !ok { - // malformed comment - return "", 0 - } - if bytes.Contains(comment, newline) { - return "", 0 - } - } - comment = bytes.TrimSpace(comment) - - // split comment into `import`, `"pkg"` - word, arg := parseWord(comment) - if string(word) != "import" { - return "", 0 - } - - line = 1 + bytes.Count(data[:cap(data)-cap(arg)], newline) - return strings.TrimSpace(string(arg)), line -} - -var ( - slashSlash = []byte("//") - slashStar = []byte("/*") - starSlash = []byte("*/") - newline = []byte("\n") -) - -// skipSpaceOrComment returns data with any leading spaces or comments removed. -func skipSpaceOrComment(data []byte) []byte { - for len(data) > 0 { - switch data[0] { - case ' ', '\t', '\r', '\n': - data = data[1:] - continue - case '/': - if bytes.HasPrefix(data, slashSlash) { - i := bytes.Index(data, newline) - if i < 0 { - return nil - } - data = data[i+1:] - continue - } - if bytes.HasPrefix(data, slashStar) { - data = data[2:] - i := bytes.Index(data, starSlash) - if i < 0 { - return nil - } - data = data[i+2:] - continue - } - } - break - } - return data -} - -// parseWord skips any leading spaces or comments in data -// and then parses the beginning of data as an identifier or keyword, -// returning that word and what remains after the word. -func parseWord(data []byte) (word, rest []byte) { - data = skipSpaceOrComment(data) - - // Parse past leading word characters. - rest = data - for { - r, size := utf8.DecodeRune(rest) - if unicode.IsLetter(r) || '0' <= r && r <= '9' || r == '_' { - rest = rest[size:] - continue - } - break - } - - word = data[:len(data)-len(rest)] - if len(word) == 0 { - return nil, nil - } - - return word, rest -} - -// MatchFile reports whether the file with the given name in the given directory -// matches the context and would be included in a Package created by ImportDir -// of that directory. -// -// MatchFile considers the name of the file and may use ctxt.OpenFile to -// read some or all of the file's content. -func (ctxt *Context) MatchFile(dir, name string) (match bool, err error) { - info, err := ctxt.matchFile(dir, name, nil, nil, nil) - return info != nil, err -} - -var dummyPkg Package - -// fileInfo records information learned about a file included in a build. -type fileInfo struct { - name string // full name including dir - header []byte - fset *token.FileSet - parsed *ast.File - parseErr error - imports []fileImport - embeds []fileEmbed - embedErr error -} - -type fileImport struct { - path string - pos token.Pos - doc *ast.CommentGroup -} - -type fileEmbed struct { - pattern string - pos token.Position -} - -// matchFile determines whether the file with the given name in the given directory -// should be included in the package being constructed. -// If the file should be included, matchFile returns a non-nil *fileInfo (and a nil error). -// Non-nil errors are reserved for unexpected problems. -// -// If name denotes a Go program, matchFile reads until the end of the -// imports and returns that section of the file in the fileInfo's header field, -// even though it only considers text until the first non-comment -// for +build lines. -// -// If allTags is non-nil, matchFile records any encountered build tag -// by setting allTags[tag] = true. -func (ctxt *Context) matchFile(dir, name string, allTags map[string]bool, binaryOnly *bool, fset *token.FileSet) (*fileInfo, error) { - if strings.HasPrefix(name, "_") || - strings.HasPrefix(name, ".") { - return nil, nil - } - - i := strings.LastIndex(name, ".") - if i < 0 { - i = len(name) - } - ext := name[i:] - - if !ctxt.goodOSArchFile(name, allTags) && !ctxt.UseAllFiles { - return nil, nil - } - - if ext != ".go" && fileListForExt(&dummyPkg, ext) == nil { - // skip - return nil, nil - } - - info := &fileInfo{name: ctxt.joinPath(dir, name), fset: fset} - if ext == ".syso" { - // binary, no reading - return info, nil - } - - f, err := ctxt.openFile(info.name) - if err != nil { - return nil, err - } - - if strings.HasSuffix(name, ".go") { - err = readGoInfo(f, info) - if strings.HasSuffix(name, "_test.go") { - binaryOnly = nil // ignore //go:binary-only-package comments in _test.go files - } - } else { - binaryOnly = nil // ignore //go:binary-only-package comments in non-Go sources - info.header, err = readComments(f) - } - f.Close() - if err != nil { - return nil, fmt.Errorf("read %s: %v", info.name, err) - } - - // Look for +build comments to accept or reject the file. - ok, sawBinaryOnly, err := ctxt.shouldBuild(info.header, allTags) - if err != nil { - return nil, fmt.Errorf("%s: %v", name, err) - } - if !ok && !ctxt.UseAllFiles { - return nil, nil - } - - if binaryOnly != nil && sawBinaryOnly { - *binaryOnly = true - } - - return info, nil -} - -func cleanDecls(m map[string][]token.Position) ([]string, map[string][]token.Position) { - all := make([]string, 0, len(m)) - for path := range m { - all = append(all, path) - } - sort.Strings(all) - return all, m -} - -// Import is shorthand for Default.Import. -func Import(path, srcDir string, mode ImportMode) (*Package, error) { - return Default.Import(path, srcDir, mode) -} - -// ImportDir is shorthand for Default.ImportDir. -func ImportDir(dir string, mode ImportMode) (*Package, error) { - return Default.ImportDir(dir, mode) -} - -var ( - bPlusBuild = []byte("+build") - - goBuildComment = []byte("//go:build") - - errGoBuildWithoutBuild = errors.New("//go:build comment without // +build comment") - errMultipleGoBuild = errors.New("multiple //go:build comments") -) - -func isGoBuildComment(line []byte) bool { - if !bytes.HasPrefix(line, goBuildComment) { - return false - } - line = bytes.TrimSpace(line) - rest := line[len(goBuildComment):] - return len(rest) == 0 || len(bytes.TrimSpace(rest)) < len(rest) -} - -// Special comment denoting a binary-only package. -// See https://golang.org/design/2775-binary-only-packages -// for more about the design of binary-only packages. -var binaryOnlyComment = []byte("//go:binary-only-package") - -// shouldBuild reports whether it is okay to use this file, -// The rule is that in the file's leading run of // comments -// and blank lines, which must be followed by a blank line -// (to avoid including a Go package clause doc comment), -// lines beginning with '// +build' are taken as build directives. -// -// The file is accepted only if each such line lists something -// matching the file. For example: -// -// // +build windows linux -// -// marks the file as applicable only on Windows and Linux. -// -// For each build tag it consults, shouldBuild sets allTags[tag] = true. -// -// shouldBuild reports whether the file should be built -// and whether a //go:binary-only-package comment was found. -func (ctxt *Context) shouldBuild(content []byte, allTags map[string]bool) (shouldBuild, binaryOnly bool, err error) { - // Identify leading run of // comments and blank lines, - // which must be followed by a blank line. - // Also identify any //go:build comments. - content, goBuild, sawBinaryOnly, err := parseFileHeader(content) - if err != nil { - return false, false, err - } - - // If //go:build line is present, it controls. - // Otherwise fall back to +build processing. - switch { - case goBuild != nil: - x, err := constraint.Parse(string(goBuild)) - if err != nil { - return false, false, fmt.Errorf("parsing //go:build line: %v", err) - } - shouldBuild = ctxt.eval(x, allTags) - - default: - shouldBuild = true - p := content - for len(p) > 0 { - line := p - if i := bytes.IndexByte(line, '\n'); i >= 0 { - line, p = line[:i], p[i+1:] - } else { - p = p[len(p):] - } - line = bytes.TrimSpace(line) - if !bytes.HasPrefix(line, slashSlash) || !bytes.Contains(line, bPlusBuild) { - continue - } - text := string(line) - if !constraint.IsPlusBuild(text) { - continue - } - if x, err := constraint.Parse(text); err == nil { - if !ctxt.eval(x, allTags) { - shouldBuild = false - } - } - } - } - - return shouldBuild, sawBinaryOnly, nil -} - -func parseFileHeader(content []byte) (trimmed, goBuild []byte, sawBinaryOnly bool, err error) { - end := 0 - p := content - ended := false // found non-blank, non-// line, so stopped accepting // +build lines - inSlashStar := false // in /* */ comment - -Lines: - for len(p) > 0 { - line := p - if i := bytes.IndexByte(line, '\n'); i >= 0 { - line, p = line[:i], p[i+1:] - } else { - p = p[len(p):] - } - line = bytes.TrimSpace(line) - if len(line) == 0 && !ended { // Blank line - // Remember position of most recent blank line. - // When we find the first non-blank, non-// line, - // this "end" position marks the latest file position - // where a // +build line can appear. - // (It must appear _before_ a blank line before the non-blank, non-// line. - // Yes, that's confusing, which is part of why we moved to //go:build lines.) - // Note that ended==false here means that inSlashStar==false, - // since seeing a /* would have set ended==true. - end = len(content) - len(p) - continue Lines - } - if !bytes.HasPrefix(line, slashSlash) { // Not comment line - ended = true - } - - if !inSlashStar && isGoBuildComment(line) { - if goBuild != nil { - return nil, nil, false, errMultipleGoBuild - } - goBuild = line - } - if !inSlashStar && bytes.Equal(line, binaryOnlyComment) { - sawBinaryOnly = true - } - - Comments: - for len(line) > 0 { - if inSlashStar { - if i := bytes.Index(line, starSlash); i >= 0 { - inSlashStar = false - line = bytes.TrimSpace(line[i+len(starSlash):]) - continue Comments - } - continue Lines - } - if bytes.HasPrefix(line, slashSlash) { - continue Lines - } - if bytes.HasPrefix(line, slashStar) { - inSlashStar = true - line = bytes.TrimSpace(line[len(slashStar):]) - continue Comments - } - // Found non-comment text. - break Lines - } - } - - return content[:end], goBuild, sawBinaryOnly, nil -} - -// saveCgo saves the information from the #cgo lines in the import "C" comment. -// These lines set CFLAGS, CPPFLAGS, CXXFLAGS and LDFLAGS and pkg-config directives -// that affect the way cgo's C code is built. -func (ctxt *Context) saveCgo(filename string, di *Package, cg *ast.CommentGroup) error { - text := cg.Text() - for _, line := range strings.Split(text, "\n") { - orig := line - - // Line is - // #cgo [GOOS/GOARCH...] LDFLAGS: stuff - // - line = strings.TrimSpace(line) - if len(line) < 5 || line[:4] != "#cgo" || (line[4] != ' ' && line[4] != '\t') { - continue - } - - // Split at colon. - line, argstr, ok := stringsCut(strings.TrimSpace(line[4:]), ":") - if !ok { - return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig) - } - - // Parse GOOS/GOARCH stuff. - f := strings.Fields(line) - if len(f) < 1 { - return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig) - } - - cond, verb := f[:len(f)-1], f[len(f)-1] - if len(cond) > 0 { - ok := false - for _, c := range cond { - if ctxt.matchAuto(c, nil) { - ok = true - break - } - } - if !ok { - continue - } - } - - args, err := splitQuoted(argstr) - if err != nil { - return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig) - } - for i, arg := range args { - if arg, ok = expandSrcDir(arg, di.Dir); !ok { - return fmt.Errorf("%s: malformed #cgo argument: %s", filename, arg) - } - args[i] = arg - } - - switch verb { - case "CFLAGS", "CPPFLAGS", "CXXFLAGS", "FFLAGS", "LDFLAGS": - // Change relative paths to absolute. - ctxt.makePathsAbsolute(args, di.Dir) - } - - switch verb { - case "CFLAGS": - di.CgoCFLAGS = append(di.CgoCFLAGS, args...) - case "CPPFLAGS": - di.CgoCPPFLAGS = append(di.CgoCPPFLAGS, args...) - case "CXXFLAGS": - di.CgoCXXFLAGS = append(di.CgoCXXFLAGS, args...) - case "FFLAGS": - di.CgoFFLAGS = append(di.CgoFFLAGS, args...) - case "LDFLAGS": - di.CgoLDFLAGS = append(di.CgoLDFLAGS, args...) - case "pkg-config": - di.CgoPkgConfig = append(di.CgoPkgConfig, args...) - default: - return fmt.Errorf("%s: invalid #cgo verb: %s", filename, orig) - } - } - return nil -} - -// expandSrcDir expands any occurrence of ${SRCDIR}, making sure -// the result is safe for the shell. -func expandSrcDir(str string, srcdir string) (string, bool) { - // "\" delimited paths cause safeCgoName to fail - // so convert native paths with a different delimiter - // to "/" before starting (eg: on windows). - srcdir = filepath.ToSlash(srcdir) - - chunks := strings.Split(str, "${SRCDIR}") - if len(chunks) < 2 { - return str, safeCgoName(str) - } - ok := true - for _, chunk := range chunks { - ok = ok && (chunk == "" || safeCgoName(chunk)) - } - ok = ok && (srcdir == "" || safeCgoName(srcdir)) - res := strings.Join(chunks, srcdir) - return res, ok && res != "" -} - -// makePathsAbsolute looks for compiler options that take paths and -// makes them absolute. We do this because through the 1.8 release we -// ran the compiler in the package directory, so any relative -I or -L -// options would be relative to that directory. In 1.9 we changed to -// running the compiler in the build directory, to get consistent -// build results (issue #19964). To keep builds working, we change any -// relative -I or -L options to be absolute. -// -// Using filepath.IsAbs and filepath.Join here means the results will be -// different on different systems, but that's OK: -I and -L options are -// inherently system-dependent. -func (ctxt *Context) makePathsAbsolute(args []string, srcDir string) { - nextPath := false - for i, arg := range args { - if nextPath { - if !filepath.IsAbs(arg) { - args[i] = filepath.Join(srcDir, arg) - } - nextPath = false - } else if strings.HasPrefix(arg, "-I") || strings.HasPrefix(arg, "-L") { - if len(arg) == 2 { - nextPath = true - } else { - if !filepath.IsAbs(arg[2:]) { - args[i] = arg[:2] + filepath.Join(srcDir, arg[2:]) - } - } - } - } -} - -// NOTE: $ is not safe for the shell, but it is allowed here because of linker options like -Wl,$ORIGIN. -// We never pass these arguments to a shell (just to programs we construct argv for), so this should be okay. -// See golang.org/issue/6038. -// The @ is for OS X. See golang.org/issue/13720. -// The % is for Jenkins. See golang.org/issue/16959. -// The ! is because module paths may use them. See golang.org/issue/26716. -// The ~ and ^ are for sr.ht. See golang.org/issue/32260. -const safeString = "+-.,/0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz:$@%! ~^" - -func safeCgoName(s string) bool { - if s == "" { - return false - } - for i := 0; i < len(s); i++ { - if c := s[i]; c < utf8.RuneSelf && strings.IndexByte(safeString, c) < 0 { - return false - } - } - return true -} - -// splitQuoted splits the string s around each instance of one or more consecutive -// white space characters while taking into account quotes and escaping, and -// returns an array of substrings of s or an empty list if s contains only white space. -// Single quotes and double quotes are recognized to prevent splitting within the -// quoted region, and are removed from the resulting substrings. If a quote in s -// isn't closed err will be set and r will have the unclosed argument as the -// last element. The backslash is used for escaping. -// -// For example, the following string: -// -// a b:"c d" 'e''f' "g\"" -// -// Would be parsed as: -// -// []string{"a", "b:c d", "ef", `g"`} -func splitQuoted(s string) (r []string, err error) { - var args []string - arg := make([]rune, len(s)) - escaped := false - quoted := false - quote := '\x00' - i := 0 - for _, rune := range s { - switch { - case escaped: - escaped = false - case rune == '\\': - escaped = true - continue - case quote != '\x00': - if rune == quote { - quote = '\x00' - continue - } - case rune == '"' || rune == '\'': - quoted = true - quote = rune - continue - case unicode.IsSpace(rune): - if quoted || i > 0 { - quoted = false - args = append(args, string(arg[:i])) - i = 0 - } - continue - } - arg[i] = rune - i++ - } - if quoted || i > 0 { - args = append(args, string(arg[:i])) - } - if quote != 0 { - err = errors.New("unclosed quote") - } else if escaped { - err = errors.New("unfinished escaping") - } - return args, err -} - -// matchAuto interprets text as either a +build or //go:build expression (whichever works), -// reporting whether the expression matches the build context. -// -// matchAuto is only used for testing of tag evaluation -// and in #cgo lines, which accept either syntax. -func (ctxt *Context) matchAuto(text string, allTags map[string]bool) bool { - if strings.ContainsAny(text, "&|()") { - text = "//go:build " + text - } else { - text = "// +build " + text - } - x, err := constraint.Parse(text) - if err != nil { - return false - } - return ctxt.eval(x, allTags) -} - -func (ctxt *Context) eval(x constraint.Expr, allTags map[string]bool) bool { - return x.Eval(func(tag string) bool { return ctxt.matchTag(tag, allTags) }) -} - -// matchTag reports whether the name is one of: -// -// cgo (if cgo is enabled) -// $GOOS -// $GOARCH -// ctxt.Compiler -// linux (if GOOS = android) -// solaris (if GOOS = illumos) -// tag (if tag is listed in ctxt.BuildTags or ctxt.ReleaseTags) -// -// It records all consulted tags in allTags. -func (ctxt *Context) matchTag(name string, allTags map[string]bool) bool { - if allTags != nil { - allTags[name] = true - } - - // special tags - if ctxt.CgoEnabled && name == "cgo" { - return true - } - if name == ctxt.GOOS || name == ctxt.GOARCH || name == ctxt.Compiler { - return true - } - if ctxt.GOOS == "android" && name == "linux" { - return true - } - if ctxt.GOOS == "illumos" && name == "solaris" { - return true - } - if ctxt.GOOS == "ios" && name == "darwin" { - return true - } - - // other tags - for _, tag := range ctxt.BuildTags { - if tag == name { - return true - } - } - for _, tag := range ctxt.ToolTags { - if tag == name { - return true - } - } - for _, tag := range ctxt.ReleaseTags { - if tag == name { - return true - } - } - - return false -} - -// goodOSArchFile returns false if the name contains a $GOOS or $GOARCH -// suffix which does not match the current system. -// The recognized name formats are: -// -// name_$(GOOS).* -// name_$(GOARCH).* -// name_$(GOOS)_$(GOARCH).* -// name_$(GOOS)_test.* -// name_$(GOARCH)_test.* -// name_$(GOOS)_$(GOARCH)_test.* -// -// Exceptions: -// if GOOS=android, then files with GOOS=linux are also matched. -// if GOOS=illumos, then files with GOOS=solaris are also matched. -// if GOOS=ios, then files with GOOS=darwin are also matched. -func (ctxt *Context) goodOSArchFile(name string, allTags map[string]bool) bool { - name, _, _ = stringsCut(name, ".") - - // Before Go 1.4, a file called "linux.go" would be equivalent to having a - // build tag "linux" in that file. For Go 1.4 and beyond, we require this - // auto-tagging to apply only to files with a non-empty prefix, so - // "foo_linux.go" is tagged but "linux.go" is not. This allows new operating - // systems, such as android, to arrive without breaking existing code with - // innocuous source code in "android.go". The easiest fix: cut everything - // in the name before the initial _. - i := strings.Index(name, "_") - if i < 0 { - return true - } - name = name[i:] // ignore everything before first _ - - l := strings.Split(name, "_") - if n := len(l); n > 0 && l[n-1] == "test" { - l = l[:n-1] - } - n := len(l) - if n >= 2 && knownOS[l[n-2]] && knownArch[l[n-1]] { - return ctxt.matchTag(l[n-1], allTags) && ctxt.matchTag(l[n-2], allTags) - } - if n >= 1 && (knownOS[l[n-1]] || knownArch[l[n-1]]) { - return ctxt.matchTag(l[n-1], allTags) - } - return true -} - -var knownOS = make(map[string]bool) -var knownArch = make(map[string]bool) - -func init() { - for _, v := range strings.Fields(goosList) { - knownOS[v] = true - } - for _, v := range strings.Fields(goarchList) { - knownArch[v] = true - } -} - -// ToolDir is the directory containing build tools. -var ToolDir = getToolDir() - -// IsLocalImport reports whether the import path is -// a local import path, like ".", "..", "./foo", or "../foo". -func IsLocalImport(path string) bool { - return path == "." || path == ".." || - strings.HasPrefix(path, "./") || strings.HasPrefix(path, "../") -} - -// ArchChar returns "?" and an error. -// In earlier versions of Go, the returned string was used to derive -// the compiler and linker tool names, the default object file suffix, -// and the default linker output name. As of Go 1.5, those strings -// no longer vary by architecture; they are compile, link, .o, and a.out, respectively. -func ArchChar(goarch string) (string, error) { - return "?", errors.New("architecture letter no longer used") -} - -func bytesCut(s, sep []byte) (before, after []byte, found bool) { - if i := bytes.Index(s, sep); i >= 0 { - return s[:i], s[i+len(sep):], true - } - return s, nil, false -} - -func stringsCut(s, sep string) (before, after string, found bool) { - if i := strings.Index(s, sep); i >= 0 { - return s[:i], s[i+len(sep):], true - } - return s, "", false -} diff --git a/internal/backport/go/build/doc.go b/internal/backport/go/build/doc.go deleted file mode 100644 index 262f6709af..0000000000 --- a/internal/backport/go/build/doc.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2011 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 build gathers information about Go packages. -// -// # Go Path -// -// The Go path is a list of directory trees containing Go source code. -// It is consulted to resolve imports that cannot be found in the standard -// Go tree. The default path is the value of the GOPATH environment -// variable, interpreted as a path list appropriate to the operating system -// (on Unix, the variable is a colon-separated string; -// on Windows, a semicolon-separated string; -// on Plan 9, a list). -// -// Each directory listed in the Go path must have a prescribed structure: -// -// The src/ directory holds source code. The path below 'src' determines -// the import path or executable name. -// -// The pkg/ directory holds installed package objects. -// As in the Go tree, each target operating system and -// architecture pair has its own subdirectory of pkg -// (pkg/GOOS_GOARCH). -// -// If DIR is a directory listed in the Go path, a package with -// source in DIR/src/foo/bar can be imported as "foo/bar" and -// has its compiled form installed to "DIR/pkg/GOOS_GOARCH/foo/bar.a" -// (or, for gccgo, "DIR/pkg/gccgo/foo/libbar.a"). -// -// The bin/ directory holds compiled commands. -// Each command is named for its source directory, but only -// using the final element, not the entire path. That is, the -// command with source in DIR/src/foo/quux is installed into -// DIR/bin/quux, not DIR/bin/foo/quux. The foo/ is stripped -// so that you can add DIR/bin to your PATH to get at the -// installed commands. -// -// Here's an example directory layout: -// -// GOPATH=/home/user/gocode -// -// /home/user/gocode/ -// src/ -// foo/ -// bar/ (go code in package bar) -// x.go -// quux/ (go code in package main) -// y.go -// bin/ -// quux (installed command) -// pkg/ -// linux_amd64/ -// foo/ -// bar.a (installed package object) -// -// # Build Constraints -// -// A build constraint, also known as a build tag, is a line comment that begins -// -// //go:build -// -// that lists the conditions under which a file should be included in the -// package. Build constraints may also be part of a file's name -// (for example, source_windows.go will only be included if the target -// operating system is windows). -// -// See 'go help buildconstraint' -// (https://golang.org/cmd/go/#hdr-Build_constraints) for details. -// -// # Binary-Only Packages -// -// In Go 1.12 and earlier, it was possible to distribute packages in binary -// form without including the source code used for compiling the package. -// The package was distributed with a source file not excluded by build -// constraints and containing a "//go:binary-only-package" comment. Like a -// build constraint, this comment appeared at the top of a file, preceded -// only by blank lines and other line comments and with a blank line -// following the comment, to separate it from the package documentation. -// Unlike build constraints, this comment is only recognized in non-test -// Go source files. -// -// The minimal source code for a binary-only package was therefore: -// -// //go:binary-only-package -// -// package mypkg -// -// The source code could include additional Go code. That code was never -// compiled but would be processed by tools like godoc and might be useful -// as end-user documentation. -// -// "go build" and other commands no longer support binary-only-packages. -// Import and ImportDir will still set the BinaryOnly flag in packages -// containing these comments for use in tools and error messages. -package build diff --git a/internal/backport/go/build/gc.go b/internal/backport/go/build/gc.go deleted file mode 100644 index e16e186e0d..0000000000 --- a/internal/backport/go/build/gc.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2018 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. - -//go:build gc -// +build gc - -package build - -import ( - "path/filepath" - "runtime" -) - -// getToolDir returns the default value of ToolDir. -func getToolDir() string { - return filepath.Join(runtime.GOROOT(), "pkg/tool/"+runtime.GOOS+"_"+runtime.GOARCH) -} diff --git a/internal/backport/go/build/gccgo.go b/internal/backport/go/build/gccgo.go deleted file mode 100644 index c8ec7041fb..0000000000 --- a/internal/backport/go/build/gccgo.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2018 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. - -//go:build gccgo -// +build gccgo - -package build - -import "runtime" - -// getToolDir returns the default value of ToolDir. -func getToolDir() string { - return envOr("GCCGOTOOLDIR", runtime.GCCGOTOOLDIR) -} diff --git a/internal/backport/go/build/read.go b/internal/backport/go/build/read.go deleted file mode 100644 index e8fe9a08b2..0000000000 --- a/internal/backport/go/build/read.go +++ /dev/null @@ -1,578 +0,0 @@ -// Copyright 2012 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 build - -import ( - "bufio" - "bytes" - "errors" - "fmt" - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/parser" - "golang.org/x/website/internal/backport/go/token" - "io" - "strconv" - "strings" - "unicode" - "unicode/utf8" -) - -type importReader struct { - b *bufio.Reader - buf []byte - peek byte - err error - eof bool - nerr int - pos token.Position -} - -var bom = []byte{0xef, 0xbb, 0xbf} - -func newImportReader(name string, r io.Reader) *importReader { - b := bufio.NewReader(r) - // Remove leading UTF-8 BOM. - // Per https://golang.org/ref/spec#Source_code_representation: - // a compiler may ignore a UTF-8-encoded byte order mark (U+FEFF) - // if it is the first Unicode code point in the source text. - if leadingBytes, err := b.Peek(3); err == nil && bytes.Equal(leadingBytes, bom) { - b.Discard(3) - } - return &importReader{ - b: b, - pos: token.Position{ - Filename: name, - Line: 1, - Column: 1, - }, - } -} - -func isIdent(c byte) bool { - return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c >= utf8.RuneSelf -} - -var ( - errSyntax = errors.New("syntax error") - errNUL = errors.New("unexpected NUL in input") -) - -// syntaxError records a syntax error, but only if an I/O error has not already been recorded. -func (r *importReader) syntaxError() { - if r.err == nil { - r.err = errSyntax - } -} - -// readByte reads the next byte from the input, saves it in buf, and returns it. -// If an error occurs, readByte records the error in r.err and returns 0. -func (r *importReader) readByte() byte { - c, err := r.b.ReadByte() - if err == nil { - r.buf = append(r.buf, c) - if c == 0 { - err = errNUL - } - } - if err != nil { - if err == io.EOF { - r.eof = true - } else if r.err == nil { - r.err = err - } - c = 0 - } - return c -} - -// readByteNoBuf is like readByte but doesn't buffer the byte. -// It exhausts r.buf before reading from r.b. -func (r *importReader) readByteNoBuf() byte { - var c byte - var err error - if len(r.buf) > 0 { - c = r.buf[0] - r.buf = r.buf[1:] - } else { - c, err = r.b.ReadByte() - if err == nil && c == 0 { - err = errNUL - } - } - - if err != nil { - if err == io.EOF { - r.eof = true - } else if r.err == nil { - r.err = err - } - return 0 - } - r.pos.Offset++ - if c == '\n' { - r.pos.Line++ - r.pos.Column = 1 - } else { - r.pos.Column++ - } - return c -} - -// peekByte returns the next byte from the input reader but does not advance beyond it. -// If skipSpace is set, peekByte skips leading spaces and comments. -func (r *importReader) peekByte(skipSpace bool) byte { - if r.err != nil { - if r.nerr++; r.nerr > 10000 { - panic("go/build: import reader looping") - } - return 0 - } - - // Use r.peek as first input byte. - // Don't just return r.peek here: it might have been left by peekByte(false) - // and this might be peekByte(true). - c := r.peek - if c == 0 { - c = r.readByte() - } - for r.err == nil && !r.eof { - if skipSpace { - // For the purposes of this reader, semicolons are never necessary to - // understand the input and are treated as spaces. - switch c { - case ' ', '\f', '\t', '\r', '\n', ';': - c = r.readByte() - continue - - case '/': - c = r.readByte() - if c == '/' { - for c != '\n' && r.err == nil && !r.eof { - c = r.readByte() - } - } else if c == '*' { - var c1 byte - for (c != '*' || c1 != '/') && r.err == nil { - if r.eof { - r.syntaxError() - } - c, c1 = c1, r.readByte() - } - } else { - r.syntaxError() - } - c = r.readByte() - continue - } - } - break - } - r.peek = c - return r.peek -} - -// nextByte is like peekByte but advances beyond the returned byte. -func (r *importReader) nextByte(skipSpace bool) byte { - c := r.peekByte(skipSpace) - r.peek = 0 - return c -} - -var goEmbed = []byte("go:embed") - -// findEmbed advances the input reader to the next //go:embed comment. -// It reports whether it found a comment. -// (Otherwise it found an error or EOF.) -func (r *importReader) findEmbed(first bool) bool { - // The import block scan stopped after a non-space character, - // so the reader is not at the start of a line on the first call. - // After that, each //go:embed extraction leaves the reader - // at the end of a line. - startLine := !first - var c byte - for r.err == nil && !r.eof { - c = r.readByteNoBuf() - Reswitch: - switch c { - default: - startLine = false - - case '\n': - startLine = true - - case ' ', '\t': - // leave startLine alone - - case '"': - startLine = false - for r.err == nil { - if r.eof { - r.syntaxError() - } - c = r.readByteNoBuf() - if c == '\\' { - r.readByteNoBuf() - if r.err != nil { - r.syntaxError() - return false - } - continue - } - if c == '"' { - c = r.readByteNoBuf() - goto Reswitch - } - } - goto Reswitch - - case '`': - startLine = false - for r.err == nil { - if r.eof { - r.syntaxError() - } - c = r.readByteNoBuf() - if c == '`' { - c = r.readByteNoBuf() - goto Reswitch - } - } - - case '\'': - startLine = false - for r.err == nil { - if r.eof { - r.syntaxError() - } - c = r.readByteNoBuf() - if c == '\\' { - r.readByteNoBuf() - if r.err != nil { - r.syntaxError() - return false - } - continue - } - if c == '\'' { - c = r.readByteNoBuf() - goto Reswitch - } - } - - case '/': - c = r.readByteNoBuf() - switch c { - default: - startLine = false - goto Reswitch - - case '*': - var c1 byte - for (c != '*' || c1 != '/') && r.err == nil { - if r.eof { - r.syntaxError() - } - c, c1 = c1, r.readByteNoBuf() - } - startLine = false - - case '/': - if startLine { - // Try to read this as a //go:embed comment. - for i := range goEmbed { - c = r.readByteNoBuf() - if c != goEmbed[i] { - goto SkipSlashSlash - } - } - c = r.readByteNoBuf() - if c == ' ' || c == '\t' { - // Found one! - return true - } - } - SkipSlashSlash: - for c != '\n' && r.err == nil && !r.eof { - c = r.readByteNoBuf() - } - startLine = true - } - } - } - return false -} - -// readKeyword reads the given keyword from the input. -// If the keyword is not present, readKeyword records a syntax error. -func (r *importReader) readKeyword(kw string) { - r.peekByte(true) - for i := 0; i < len(kw); i++ { - if r.nextByte(false) != kw[i] { - r.syntaxError() - return - } - } - if isIdent(r.peekByte(false)) { - r.syntaxError() - } -} - -// readIdent reads an identifier from the input. -// If an identifier is not present, readIdent records a syntax error. -func (r *importReader) readIdent() { - c := r.peekByte(true) - if !isIdent(c) { - r.syntaxError() - return - } - for isIdent(r.peekByte(false)) { - r.peek = 0 - } -} - -// readString reads a quoted string literal from the input. -// If an identifier is not present, readString records a syntax error. -func (r *importReader) readString() { - switch r.nextByte(true) { - case '`': - for r.err == nil { - if r.nextByte(false) == '`' { - break - } - if r.eof { - r.syntaxError() - } - } - case '"': - for r.err == nil { - c := r.nextByte(false) - if c == '"' { - break - } - if r.eof || c == '\n' { - r.syntaxError() - } - if c == '\\' { - r.nextByte(false) - } - } - default: - r.syntaxError() - } -} - -// readImport reads an import clause - optional identifier followed by quoted string - -// from the input. -func (r *importReader) readImport() { - c := r.peekByte(true) - if c == '.' { - r.peek = 0 - } else if isIdent(c) { - r.readIdent() - } - r.readString() -} - -// readComments is like io.ReadAll, except that it only reads the leading -// block of comments in the file. -func readComments(f io.Reader) ([]byte, error) { - r := newImportReader("", f) - r.peekByte(true) - if r.err == nil && !r.eof { - // Didn't reach EOF, so must have found a non-space byte. Remove it. - r.buf = r.buf[:len(r.buf)-1] - } - return r.buf, r.err -} - -// readGoInfo expects a Go file as input and reads the file up to and including the import section. -// It records what it learned in *info. -// If info.fset is non-nil, readGoInfo parses the file and sets info.parsed, info.parseErr, -// info.imports, info.embeds, and info.embedErr. -// -// It only returns an error if there are problems reading the file, -// not for syntax errors in the file itself. -func readGoInfo(f io.Reader, info *fileInfo) error { - r := newImportReader(info.name, f) - - r.readKeyword("package") - r.readIdent() - for r.peekByte(true) == 'i' { - r.readKeyword("import") - if r.peekByte(true) == '(' { - r.nextByte(false) - for r.peekByte(true) != ')' && r.err == nil { - r.readImport() - } - r.nextByte(false) - } else { - r.readImport() - } - } - - info.header = r.buf - - // If we stopped successfully before EOF, we read a byte that told us we were done. - // Return all but that last byte, which would cause a syntax error if we let it through. - if r.err == nil && !r.eof { - info.header = r.buf[:len(r.buf)-1] - } - - // If we stopped for a syntax error, consume the whole file so that - // we are sure we don't change the errors that go/parser returns. - if r.err == errSyntax { - r.err = nil - for r.err == nil && !r.eof { - r.readByte() - } - info.header = r.buf - } - if r.err != nil { - return r.err - } - - if info.fset == nil { - return nil - } - - // Parse file header & record imports. - info.parsed, info.parseErr = parser.ParseFile(info.fset, info.name, info.header, parser.ImportsOnly|parser.ParseComments) - if info.parseErr != nil { - return nil - } - - hasEmbed := false - for _, decl := range info.parsed.Decls { - d, ok := decl.(*ast.GenDecl) - if !ok { - continue - } - for _, dspec := range d.Specs { - spec, ok := dspec.(*ast.ImportSpec) - if !ok { - continue - } - quoted := spec.Path.Value - path, err := strconv.Unquote(quoted) - if err != nil { - return fmt.Errorf("parser returned invalid quoted string: <%s>", quoted) - } - if path == "embed" { - hasEmbed = true - } - - doc := spec.Doc - if doc == nil && len(d.Specs) == 1 { - doc = d.Doc - } - info.imports = append(info.imports, fileImport{path, spec.Pos(), doc}) - } - } - - // If the file imports "embed", - // we have to look for //go:embed comments - // in the remainder of the file. - // The compiler will enforce the mapping of comments to - // declared variables. We just need to know the patterns. - // If there were //go:embed comments earlier in the file - // (near the package statement or imports), the compiler - // will reject them. They can be (and have already been) ignored. - if hasEmbed { - var line []byte - for first := true; r.findEmbed(first); first = false { - line = line[:0] - pos := r.pos - for { - c := r.readByteNoBuf() - if c == '\n' || r.err != nil || r.eof { - break - } - line = append(line, c) - } - // Add args if line is well-formed. - // Ignore badly-formed lines - the compiler will report them when it finds them, - // and we can pretend they are not there to help go list succeed with what it knows. - embs, err := parseGoEmbed(string(line), pos) - if err == nil { - info.embeds = append(info.embeds, embs...) - } - } - } - - return nil -} - -// parseGoEmbed parses the text following "//go:embed" to extract the glob patterns. -// It accepts unquoted space-separated patterns as well as double-quoted and back-quoted Go strings. -// This is based on a similar function in cmd/compile/internal/gc/noder.go; -// this version calculates position information as well. -func parseGoEmbed(args string, pos token.Position) ([]fileEmbed, error) { - trimBytes := func(n int) { - pos.Offset += n - pos.Column += utf8.RuneCountInString(args[:n]) - args = args[n:] - } - trimSpace := func() { - trim := strings.TrimLeftFunc(args, unicode.IsSpace) - trimBytes(len(args) - len(trim)) - } - - var list []fileEmbed - for trimSpace(); args != ""; trimSpace() { - var path string - pathPos := pos - Switch: - switch args[0] { - default: - i := len(args) - for j, c := range args { - if unicode.IsSpace(c) { - i = j - break - } - } - path = args[:i] - trimBytes(i) - - case '`': - var ok bool - path, _, ok = stringsCut(args[1:], "`") - if !ok { - return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args) - } - trimBytes(1 + len(path) + 1) - - case '"': - i := 1 - for ; i < len(args); i++ { - if args[i] == '\\' { - i++ - continue - } - if args[i] == '"' { - q, err := strconv.Unquote(args[:i+1]) - if err != nil { - return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args[:i+1]) - } - path = q - trimBytes(i + 1) - break Switch - } - } - if i >= len(args) { - return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args) - } - } - - if args != "" { - r, _ := utf8.DecodeRuneInString(args) - if !unicode.IsSpace(r) { - return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args) - } - } - list = append(list, fileEmbed{path, pathPos}) - } - return list, nil -} diff --git a/internal/backport/go/build/read_test.go b/internal/backport/go/build/read_test.go deleted file mode 100644 index f15b153017..0000000000 --- a/internal/backport/go/build/read_test.go +++ /dev/null @@ -1,360 +0,0 @@ -// Copyright 2012 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 build - -import ( - "fmt" - "io" - "strings" - "testing" - - "golang.org/x/website/internal/backport/go/token" -) - -const quote = "`" - -type readTest struct { - // Test input contains ℙ where readGoInfo should stop. - in string - err string -} - -var readGoInfoTests = []readTest{ - { - `package p`, - "", - }, - { - `package p; import "x"`, - "", - }, - { - `package p; import . "x"`, - "", - }, - { - `package p; import "x";ℙvar x = 1`, - "", - }, - { - `package p - - // comment - - import "x" - import _ "x" - import a "x" - - /* comment */ - - import ( - "x" /* comment */ - _ "x" - a "x" // comment - ` + quote + `x` + quote + ` - _ /*comment*/ ` + quote + `x` + quote + ` - a ` + quote + `x` + quote + ` - ) - import ( - ) - import () - import()import()import() - import();import();import() - - ℙvar x = 1 - `, - "", - }, - { - "\ufeff𝔻" + `package p; import "x";ℙvar x = 1`, - "", - }, -} - -var readCommentsTests = []readTest{ - { - `ℙpackage p`, - "", - }, - { - `ℙpackage p; import "x"`, - "", - }, - { - `ℙpackage p; import . "x"`, - "", - }, - { - "\ufeff𝔻" + `ℙpackage p; import . "x"`, - "", - }, - { - `// foo - - /* bar */ - - /* quux */ // baz - - /*/ zot */ - - // asdf - ℙHello, world`, - "", - }, - { - "\ufeff𝔻" + `// foo - - /* bar */ - - /* quux */ // baz - - /*/ zot */ - - // asdf - ℙHello, world`, - "", - }, -} - -func testRead(t *testing.T, tests []readTest, read func(io.Reader) ([]byte, error)) { - for i, tt := range tests { - beforeP, afterP, _ := stringsCut1(tt.in, "ℙ") - in := beforeP + afterP - testOut := beforeP - - if beforeD, afterD, ok := stringsCut1(beforeP, "𝔻"); ok { - in = beforeD + afterD + afterP - testOut = afterD - } - - r := strings.NewReader(in) - buf, err := read(r) - if err != nil { - if tt.err == "" { - t.Errorf("#%d: err=%q, expected success (%q)", i, err, string(buf)) - } else if !strings.Contains(err.Error(), tt.err) { - t.Errorf("#%d: err=%q, expected %q", i, err, tt.err) - } - continue - } - if tt.err != "" { - t.Errorf("#%d: success, expected %q", i, tt.err) - continue - } - - out := string(buf) - if out != testOut { - t.Errorf("#%d: wrong output:\nhave %q\nwant %q\n", i, out, testOut) - } - } -} - -func TestReadGoInfo(t *testing.T) { - testRead(t, readGoInfoTests, func(r io.Reader) ([]byte, error) { - var info fileInfo - err := readGoInfo(r, &info) - return info.header, err - }) -} - -func TestReadComments(t *testing.T) { - testRead(t, readCommentsTests, readComments) -} - -var readFailuresTests = []readTest{ - { - `package`, - "syntax error", - }, - { - "package p\n\x00\nimport `math`\n", - "unexpected NUL in input", - }, - { - `package p; import`, - "syntax error", - }, - { - `package p; import "`, - "syntax error", - }, - { - "package p; import ` \n\n", - "syntax error", - }, - { - `package p; import "x`, - "syntax error", - }, - { - `package p; import _`, - "syntax error", - }, - { - `package p; import _ "`, - "syntax error", - }, - { - `package p; import _ "x`, - "syntax error", - }, - { - `package p; import .`, - "syntax error", - }, - { - `package p; import . "`, - "syntax error", - }, - { - `package p; import . "x`, - "syntax error", - }, - { - `package p; import (`, - "syntax error", - }, - { - `package p; import ("`, - "syntax error", - }, - { - `package p; import ("x`, - "syntax error", - }, - { - `package p; import ("x"`, - "syntax error", - }, -} - -func TestReadFailuresIgnored(t *testing.T) { - // Syntax errors should not be reported (false arg to readImports). - // Instead, entire file should be the output and no error. - // Convert tests not to return syntax errors. - tests := make([]readTest, len(readFailuresTests)) - copy(tests, readFailuresTests) - for i := range tests { - tt := &tests[i] - if !strings.Contains(tt.err, "NUL") { - tt.err = "" - } - } - testRead(t, tests, func(r io.Reader) ([]byte, error) { - var info fileInfo - err := readGoInfo(r, &info) - return info.header, err - }) -} - -var readEmbedTests = []struct { - in, out string -}{ - { - "package p\n", - "", - }, - { - "package p\nimport \"embed\"\nvar i int\n//go:embed x y z\nvar files embed.FS", - `test:4:12:x - test:4:14:y - test:4:16:z`, - }, - { - "package p\nimport \"embed\"\nvar i int\n//go:embed x \"\\x79\" `z`\nvar files embed.FS", - `test:4:12:x - test:4:14:y - test:4:21:z`, - }, - { - "package p\nimport \"embed\"\nvar i int\n//go:embed x y\n//go:embed z\nvar files embed.FS", - `test:4:12:x - test:4:14:y - test:5:12:z`, - }, - { - "package p\nimport \"embed\"\nvar i int\n\t //go:embed x y\n\t //go:embed z\n\t var files embed.FS", - `test:4:14:x - test:4:16:y - test:5:14:z`, - }, - { - "package p\nimport \"embed\"\n//go:embed x y z\nvar files embed.FS", - `test:3:12:x - test:3:14:y - test:3:16:z`, - }, - { - "\ufeffpackage p\nimport \"embed\"\n//go:embed x y z\nvar files embed.FS", - `test:3:12:x - test:3:14:y - test:3:16:z`, - }, - { - "package p\nimport \"embed\"\nvar s = \"/*\"\n//go:embed x\nvar files embed.FS", - `test:4:12:x`, - }, - { - `package p - import "embed" - var s = "\"\\\\" - //go:embed x - var files embed.FS`, - `test:4:15:x`, - }, - { - "package p\nimport \"embed\"\nvar s = `/*`\n//go:embed x\nvar files embed.FS", - `test:4:12:x`, - }, - { - "package p\nimport \"embed\"\nvar s = z/ *y\n//go:embed pointer\nvar pointer embed.FS", - "test:4:12:pointer", - }, - { - "package p\n//go:embed x y z\n", // no import, no scan - "", - }, - { - "package p\n//go:embed x y z\nvar files embed.FS", // no import, no scan - "", - }, - { - "\ufeffpackage p\n//go:embed x y z\nvar files embed.FS", // no import, no scan - "", - }, -} - -func TestReadEmbed(t *testing.T) { - fset := token.NewFileSet() - for i, tt := range readEmbedTests { - info := fileInfo{ - name: "test", - fset: fset, - } - err := readGoInfo(strings.NewReader(tt.in), &info) - if err != nil { - t.Errorf("#%d: %v", i, err) - continue - } - b := &strings.Builder{} - sep := "" - for _, emb := range info.embeds { - fmt.Fprintf(b, "%s%v:%s", sep, emb.pos, emb.pattern) - sep = "\n" - } - got := b.String() - want := strings.Join(strings.Fields(tt.out), "\n") - if got != want { - t.Errorf("#%d: embeds:\n%s\nwant:\n%s", i, got, want) - } - } -} - -func stringsCut1(s, sep string) (before, after string, found bool) { - if i := strings.Index(s, sep); i >= 0 { - return s[:i], s[i+len(sep):], true - } - return s, "", false -} diff --git a/internal/backport/go/build/syslist.go b/internal/backport/go/build/syslist.go deleted file mode 100644 index 0f6e336925..0000000000 --- a/internal/backport/go/build/syslist.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2011 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 build - -// List of past, present, and future known GOOS and GOARCH values. -// Do not remove from this list, as these are used for go/build filename matching. - -const goosList = "aix android darwin dragonfly freebsd hurd illumos ios js linux nacl netbsd openbsd plan9 solaris windows zos " -const goarchList = "386 amd64 amd64p32 arm armbe arm64 arm64be loong64 mips mipsle mips64 mips64le mips64p32 mips64p32le ppc ppc64 ppc64le riscv riscv64 s390 s390x sparc sparc64 wasm " diff --git a/internal/backport/go/build/syslist_test.go b/internal/backport/go/build/syslist_test.go deleted file mode 100644 index 2b7b4c71aa..0000000000 --- a/internal/backport/go/build/syslist_test.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2011 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 build - -import ( - "runtime" - "testing" -) - -var ( - thisOS = runtime.GOOS - thisArch = runtime.GOARCH - otherOS = anotherOS() - otherArch = anotherArch() -) - -func anotherOS() string { - if thisOS != "darwin" && thisOS != "ios" { - return "darwin" - } - return "linux" -} - -func anotherArch() string { - if thisArch != "amd64" { - return "amd64" - } - return "386" -} - -type GoodFileTest struct { - name string - result bool -} - -var tests = []GoodFileTest{ - {"file.go", true}, - {"file.c", true}, - {"file_foo.go", true}, - {"file_" + thisArch + ".go", true}, - {"file_" + otherArch + ".go", false}, - {"file_" + thisOS + ".go", true}, - {"file_" + otherOS + ".go", false}, - {"file_" + thisOS + "_" + thisArch + ".go", true}, - {"file_" + otherOS + "_" + thisArch + ".go", false}, - {"file_" + thisOS + "_" + otherArch + ".go", false}, - {"file_" + otherOS + "_" + otherArch + ".go", false}, - {"file_foo_" + thisArch + ".go", true}, - {"file_foo_" + otherArch + ".go", false}, - {"file_" + thisOS + ".c", true}, - {"file_" + otherOS + ".c", false}, -} - -func TestGoodOSArch(t *testing.T) { - for _, test := range tests { - if Default.goodOSArchFile(test.name, make(map[string]bool)) != test.result { - t.Fatalf("goodOSArchFile(%q) != %v", test.name, test.result) - } - } -} diff --git a/internal/backport/go/doc/Makefile b/internal/backport/go/doc/Makefile deleted file mode 100644 index ca4948f91c..0000000000 --- a/internal/backport/go/doc/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright 2009 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. - -# Script to test heading detection heuristic -headscan: headscan.go - go build headscan.go diff --git a/internal/backport/go/doc/comment.go b/internal/backport/go/doc/comment.go deleted file mode 100644 index 6cf5926eb2..0000000000 --- a/internal/backport/go/doc/comment.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2009 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 doc - -import ( - "io" - - "golang.org/x/website/internal/backport/go/doc/comment" -) - -// ToHTML converts comment text to formatted HTML. -// -// Deprecated: ToHTML cannot identify documentation links -// in the doc comment, because they depend on knowing what -// package the text came from, which is not included in this API. -// -// Given the *[doc.Package] p where text was found, -// ToHTML(w, text, nil) can be replaced by: -// -// w.Write(p.HTML(text)) -// -// which is in turn shorthand for: -// -// w.Write(p.Printer().HTML(p.Parser().Parse(text))) -// -// If words may be non-nil, the longer replacement is: -// -// parser := p.Parser() -// parser.Words = words -// w.Write(p.Printer().HTML(parser.Parse(d))) -func ToHTML(w io.Writer, text string, words map[string]string) { - p := new(Package).Parser() - p.Words = words - d := p.Parse(text) - pr := new(comment.Printer) - w.Write(pr.HTML(d)) -} - -// ToText converts comment text to formatted text. -// -// Deprecated: ToText cannot identify documentation links -// in the doc comment, because they depend on knowing what -// package the text came from, which is not included in this API. -// -// Given the *[doc.Package] p where text was found, -// ToText(w, text, "", "\t", 80) can be replaced by: -// -// w.Write(p.Text(text)) -// -// In the general case, ToText(w, text, prefix, codePrefix, width) -// can be replaced by: -// -// d := p.Parser().Parse(text) -// pr := p.Printer() -// pr.TextPrefix = prefix -// pr.TextCodePrefix = codePrefix -// pr.TextWidth = width -// w.Write(pr.Text(d)) -// -// See the documentation for [Package.Text] and [comment.Printer.Text] -// for more details. -func ToText(w io.Writer, text string, prefix, codePrefix string, width int) { - d := new(Package).Parser().Parse(text) - pr := &comment.Printer{ - TextPrefix: prefix, - TextCodePrefix: codePrefix, - TextWidth: width, - } - w.Write(pr.Text(d)) -} diff --git a/internal/backport/go/doc/comment/doc.go b/internal/backport/go/doc/comment/doc.go deleted file mode 100644 index 45a476aa9a..0000000000 --- a/internal/backport/go/doc/comment/doc.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2022 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 comment implements parsing and reformatting of Go doc comments, -(documentation comments), which are comments that immediately precede -a top-level declaration of a package, const, func, type, or var. - -Go doc comment syntax is a simplified subset of Markdown that supports -links, headings, paragraphs, lists (without nesting), and preformatted text blocks. -The details of the syntax are documented at https://go.dev/doc/comment. - -To parse the text associated with a doc comment (after removing comment markers), -use a [Parser]: - - var p comment.Parser - doc := p.Parse(text) - -The result is a [*Doc]. -To reformat it as a doc comment, HTML, Markdown, or plain text, -use a [Printer]: - - var pr comment.Printer - os.Stdout.Write(pr.Text(doc)) - -The [Parser] and [Printer] types are structs whose fields can be -modified to customize the operations. -For details, see the documentation for those types. - -Use cases that need additional control over reformatting can -implement their own logic by inspecting the parsed syntax itself. -See the documentation for [Doc], [Block], [Text] for an overview -and links to additional types. -*/ -package comment diff --git a/internal/backport/go/doc/comment/html.go b/internal/backport/go/doc/comment/html.go deleted file mode 100644 index bc076f6a58..0000000000 --- a/internal/backport/go/doc/comment/html.go +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright 2022 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 comment - -import ( - "bytes" - "fmt" - "strconv" -) - -// An htmlPrinter holds the state needed for printing a Doc as HTML. -type htmlPrinter struct { - *Printer - tight bool -} - -// HTML returns an HTML formatting of the Doc. -// See the [Printer] documentation for ways to customize the HTML output. -func (p *Printer) HTML(d *Doc) []byte { - hp := &htmlPrinter{Printer: p} - var out bytes.Buffer - for _, x := range d.Content { - hp.block(&out, x) - } - return out.Bytes() -} - -// block prints the block x to out. -func (p *htmlPrinter) block(out *bytes.Buffer, x Block) { - switch x := x.(type) { - default: - fmt.Fprintf(out, "?%T", x) - - case *Paragraph: - if !p.tight { - out.WriteString("

") - } - p.text(out, x.Text) - out.WriteString("\n") - - case *Heading: - out.WriteString("") - p.text(out, x.Text) - out.WriteString("\n") - - case *Code: - out.WriteString("

")
-		p.escape(out, x.Text)
-		out.WriteString("
\n") - - case *List: - kind := "ol>\n" - if x.Items[0].Number == "" { - kind = "ul>\n" - } - out.WriteString("<") - out.WriteString(kind) - next := "1" - for _, item := range x.Items { - out.WriteString("") - p.tight = !x.BlankBetween() - for _, blk := range item.Content { - p.block(out, blk) - } - p.tight = false - } - out.WriteString("= 0; i-- { - if b[i] < '9' { - b[i]++ - return string(b) - } - b[i] = '0' - } - return "1" + string(b) -} - -// text prints the text sequence x to out. -func (p *htmlPrinter) text(out *bytes.Buffer, x []Text) { - for _, t := range x { - switch t := t.(type) { - case Plain: - p.escape(out, string(t)) - case Italic: - out.WriteString("") - p.escape(out, string(t)) - out.WriteString("") - case *Link: - out.WriteString(``) - p.text(out, t.Text) - out.WriteString("") - case *DocLink: - url := p.docLinkURL(t) - if url != "" { - out.WriteString(``) - } - p.text(out, t.Text) - if url != "" { - out.WriteString("") - } - } - } -} - -// escape prints s to out as plain text, -// escaping < & " ' and > to avoid being misinterpreted -// in larger HTML constructs. -func (p *htmlPrinter) escape(out *bytes.Buffer, s string) { - start := 0 - for i := 0; i < len(s); i++ { - switch s[i] { - case '<': - out.WriteString(s[start:i]) - out.WriteString("<") - start = i + 1 - case '&': - out.WriteString(s[start:i]) - out.WriteString("&") - start = i + 1 - case '"': - out.WriteString(s[start:i]) - out.WriteString(""") - start = i + 1 - case '\'': - out.WriteString(s[start:i]) - out.WriteString("'") - start = i + 1 - case '>': - out.WriteString(s[start:i]) - out.WriteString(">") - start = i + 1 - } - } - out.WriteString(s[start:]) -} diff --git a/internal/backport/go/doc/comment/markdown.go b/internal/backport/go/doc/comment/markdown.go deleted file mode 100644 index 26937a4cdb..0000000000 --- a/internal/backport/go/doc/comment/markdown.go +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright 2022 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 comment - -import ( - "bytes" - "fmt" - "strings" -) - -// An mdPrinter holds the state needed for printing a Doc as Markdown. -type mdPrinter struct { - *Printer - headingPrefix string - raw bytes.Buffer -} - -// Markdown returns a Markdown formatting of the Doc. -// See the [Printer] documentation for ways to customize the Markdown output. -func (p *Printer) Markdown(d *Doc) []byte { - mp := &mdPrinter{ - Printer: p, - headingPrefix: strings.Repeat("#", p.headingLevel()) + " ", - } - - var out bytes.Buffer - for i, x := range d.Content { - if i > 0 { - out.WriteByte('\n') - } - mp.block(&out, x) - } - return out.Bytes() -} - -// block prints the block x to out. -func (p *mdPrinter) block(out *bytes.Buffer, x Block) { - switch x := x.(type) { - default: - fmt.Fprintf(out, "?%T", x) - - case *Paragraph: - p.text(out, x.Text) - out.WriteString("\n") - - case *Heading: - out.WriteString(p.headingPrefix) - p.text(out, x.Text) - if id := p.headingID(x); id != "" { - out.WriteString(" {#") - out.WriteString(id) - out.WriteString("}") - } - out.WriteString("\n") - - case *Code: - md := x.Text - for md != "" { - var line string - line, md, _ = stringsCut(md, "\n") - if line != "" { - out.WriteString("\t") - out.WriteString(line) - } - out.WriteString("\n") - } - - case *List: - loose := x.BlankBetween() - for i, item := range x.Items { - if i > 0 && loose { - out.WriteString("\n") - } - if n := item.Number; n != "" { - out.WriteString(" ") - out.WriteString(n) - out.WriteString(". ") - } else { - out.WriteString(" - ") // SP SP - SP - } - for i, blk := range item.Content { - const fourSpace = " " - if i > 0 { - out.WriteString("\n" + fourSpace) - } - p.text(out, blk.(*Paragraph).Text) - out.WriteString("\n") - } - } - } -} - -// text prints the text sequence x to out. -func (p *mdPrinter) text(out *bytes.Buffer, x []Text) { - p.raw.Reset() - p.rawText(&p.raw, x) - line := bytes.TrimSpace(p.raw.Bytes()) - if len(line) == 0 { - return - } - switch line[0] { - case '+', '-', '*', '#': - // Escape what would be the start of an unordered list or heading. - out.WriteByte('\\') - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - i := 1 - for i < len(line) && '0' <= line[i] && line[i] <= '9' { - i++ - } - if i < len(line) && (line[i] == '.' || line[i] == ')') { - // Escape what would be the start of an ordered list. - out.Write(line[:i]) - out.WriteByte('\\') - line = line[i:] - } - } - out.Write(line) -} - -// rawText prints the text sequence x to out, -// without worrying about escaping characters -// that have special meaning at the start of a Markdown line. -func (p *mdPrinter) rawText(out *bytes.Buffer, x []Text) { - for _, t := range x { - switch t := t.(type) { - case Plain: - p.escape(out, string(t)) - case Italic: - out.WriteString("*") - p.escape(out, string(t)) - out.WriteString("*") - case *Link: - out.WriteString("[") - p.rawText(out, t.Text) - out.WriteString("](") - out.WriteString(t.URL) - out.WriteString(")") - case *DocLink: - url := p.docLinkURL(t) - if url != "" { - out.WriteString("[") - } - p.rawText(out, t.Text) - if url != "" { - out.WriteString("](") - url = strings.ReplaceAll(url, "(", "%28") - url = strings.ReplaceAll(url, ")", "%29") - out.WriteString(url) - out.WriteString(")") - } - } - } -} - -// escape prints s to out as plain text, -// escaping special characters to avoid being misinterpreted -// as Markdown markup sequences. -func (p *mdPrinter) escape(out *bytes.Buffer, s string) { - start := 0 - for i := 0; i < len(s); i++ { - switch s[i] { - case '\n': - // Turn all \n into spaces, for a few reasons: - // - Avoid introducing paragraph breaks accidentally. - // - Avoid the need to reindent after the newline. - // - Avoid problems with Markdown renderers treating - // every mid-paragraph newline as a
. - out.WriteString(s[start:i]) - out.WriteByte(' ') - start = i + 1 - continue - case '`', '_', '*', '[', '<', '\\': - // Not all of these need to be escaped all the time, - // but is valid and easy to do so. - // We assume the Markdown is being passed to a - // Markdown renderer, not edited by a person, - // so it's fine to have escapes that are not strictly - // necessary in some cases. - out.WriteString(s[start:i]) - out.WriteByte('\\') - out.WriteByte(s[i]) - start = i + 1 - } - } - out.WriteString(s[start:]) -} diff --git a/internal/backport/go/doc/comment/mkstd.sh b/internal/backport/go/doc/comment/mkstd.sh deleted file mode 100755 index c9dee8c55e..0000000000 --- a/internal/backport/go/doc/comment/mkstd.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash -# Copyright 2022 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. - -# This could be a good use for embed but go/doc/comment -# is built into the bootstrap go command, so it can't use embed. -# Also not using embed lets us emit a string array directly -# and avoid init-time work. - -( -echo "// Copyright 2022 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. - -// Code generated by 'go generate' DO NOT EDIT. -//go:generate ./mkstd.sh - -package comment - -var stdPkgs = []string{" -go list std | grep -v / | sort | sed 's/.*/"&",/' -echo "}" -) | gofmt >std.go.tmp && mv std.go.tmp std.go diff --git a/internal/backport/go/doc/comment/old_test.go b/internal/backport/go/doc/comment/old_test.go deleted file mode 100644 index 944f94d16d..0000000000 --- a/internal/backport/go/doc/comment/old_test.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2011 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. - -// These tests are carried forward from the old go/doc implementation. - -package comment - -import "testing" - -var oldHeadingTests = []struct { - line string - ok bool -}{ - {"Section", true}, - {"A typical usage", true}, - {"ΔΛΞ is Greek", true}, - {"Foo 42", true}, - {"", false}, - {"section", false}, - {"A typical usage:", false}, - {"This code:", false}, - {"δ is Greek", false}, - {"Foo §", false}, - {"Fermat's Last Sentence", true}, - {"Fermat's", true}, - {"'sX", false}, - {"Ted 'Too' Bar", false}, - {"Use n+m", false}, - {"Scanning:", false}, - {"N:M", false}, -} - -func TestIsOldHeading(t *testing.T) { - for _, tt := range oldHeadingTests { - if isOldHeading(tt.line, []string{"Text.", "", tt.line, "", "Text."}, 2) != tt.ok { - t.Errorf("isOldHeading(%q) = %v, want %v", tt.line, !tt.ok, tt.ok) - } - } -} - -var autoURLTests = []struct { - in, out string -}{ - {"", ""}, - {"http://[::1]:8080/foo.txt", "http://[::1]:8080/foo.txt"}, - {"https://www.google.com) after", "https://www.google.com"}, - {"https://www.google.com:30/x/y/z:b::c. After", "https://www.google.com:30/x/y/z:b::c"}, - {"http://www.google.com/path/:;!-/?query=%34b#093124", "http://www.google.com/path/:;!-/?query=%34b#093124"}, - {"http://www.google.com/path/:;!-/?query=%34bar#093124", "http://www.google.com/path/:;!-/?query=%34bar#093124"}, - {"http://www.google.com/index.html! After", "http://www.google.com/index.html"}, - {"http://www.google.com/", "http://www.google.com/"}, - {"https://www.google.com/", "https://www.google.com/"}, - {"http://www.google.com/path.", "http://www.google.com/path"}, - {"http://en.wikipedia.org/wiki/Camellia_(cipher)", "http://en.wikipedia.org/wiki/Camellia_(cipher)"}, - {"http://www.google.com/)", "http://www.google.com/"}, - {"http://gmail.com)", "http://gmail.com"}, - {"http://gmail.com))", "http://gmail.com"}, - {"http://gmail.com ((http://gmail.com)) ()", "http://gmail.com"}, - {"http://example.com/ quux!", "http://example.com/"}, - {"http://example.com/%2f/ /world.", "http://example.com/%2f/"}, - {"http: ipsum //host/path", ""}, - {"javascript://is/not/linked", ""}, - {"http://foo", "http://foo"}, - {"https://www.example.com/person/][Person Name]]", "https://www.example.com/person/"}, - {"http://golang.org/)", "http://golang.org/"}, - {"http://golang.org/hello())", "http://golang.org/hello()"}, - {"http://git.qemu.org/?p=qemu.git;a=blob;f=qapi-schema.json;hb=HEAD", "http://git.qemu.org/?p=qemu.git;a=blob;f=qapi-schema.json;hb=HEAD"}, - {"https://foo.bar/bal/x(])", "https://foo.bar/bal/x"}, // inner ] causes (]) to be cut off from URL - {"http://bar(])", "http://bar"}, // same -} - -func TestAutoURL(t *testing.T) { - for _, tt := range autoURLTests { - url, ok := autoURL(tt.in) - if url != tt.out || ok != (tt.out != "") { - t.Errorf("autoURL(%q) = %q, %v, want %q, %v", tt.in, url, ok, tt.out, tt.out != "") - } - } -} diff --git a/internal/backport/go/doc/comment/parse.go b/internal/backport/go/doc/comment/parse.go deleted file mode 100644 index 5448ab3100..0000000000 --- a/internal/backport/go/doc/comment/parse.go +++ /dev/null @@ -1,1271 +0,0 @@ -// Copyright 2022 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 comment - -import ( - "sort" - "strings" - "unicode" - "unicode/utf8" -) - -// A Doc is a parsed Go doc comment. -type Doc struct { - // Content is the sequence of content blocks in the comment. - Content []Block - - // Links is the link definitions in the comment. - Links []*LinkDef -} - -// A LinkDef is a single link definition. -type LinkDef struct { - Text string // the link text - URL string // the link URL - Used bool // whether the comment uses the definition -} - -// A Block is block-level content in a doc comment, -// one of [*Code], [*Heading], [*List], or [*Paragraph]. -type Block interface { - block() -} - -// A Heading is a doc comment heading. -type Heading struct { - Text []Text // the heading text -} - -func (*Heading) block() {} - -// A List is a numbered or bullet list. -// Lists are always non-empty: len(Items) > 0. -// In a numbered list, every Items[i].Number is a non-empty string. -// In a bullet list, every Items[i].Number is an empty string. -type List struct { - // Items is the list items. - Items []*ListItem - - // ForceBlankBefore indicates that the list must be - // preceded by a blank line when reformatting the comment, - // overriding the usual conditions. See the BlankBefore method. - // - // The comment parser sets ForceBlankBefore for any list - // that is preceded by a blank line, to make sure - // the blank line is preserved when printing. - ForceBlankBefore bool - - // ForceBlankBetween indicates that list items must be - // separated by blank lines when reformatting the comment, - // overriding the usual conditions. See the BlankBetween method. - // - // The comment parser sets ForceBlankBetween for any list - // that has a blank line between any two of its items, to make sure - // the blank lines are preserved when printing. - ForceBlankBetween bool -} - -func (*List) block() {} - -// BlankBefore reports whether a reformatting of the comment -// should include a blank line before the list. -// The default rule is the same as for [BlankBetween]: -// if the list item content contains any blank lines -// (meaning at least one item has multiple paragraphs) -// then the list itself must be preceded by a blank line. -// A preceding blank line can be forced by setting [List].ForceBlankBefore. -func (l *List) BlankBefore() bool { - return l.ForceBlankBefore || l.BlankBetween() -} - -// BlankBetween reports whether a reformatting of the comment -// should include a blank line between each pair of list items. -// The default rule is that if the list item content contains any blank lines -// (meaning at least one item has multiple paragraphs) -// then list items must themselves be separated by blank lines. -// Blank line separators can be forced by setting [List].ForceBlankBetween. -func (l *List) BlankBetween() bool { - if l.ForceBlankBetween { - return true - } - for _, item := range l.Items { - if len(item.Content) != 1 { - // Unreachable for parsed comments today, - // since the only way to get multiple item.Content - // is multiple paragraphs, which must have been - // separated by a blank line. - return true - } - } - return false -} - -// A ListItem is a single item in a numbered or bullet list. -type ListItem struct { - // Number is a decimal string in a numbered list - // or an empty string in a bullet list. - Number string // "1", "2", ...; "" for bullet list - - // Content is the list content. - // Currently, restrictions in the parser and printer - // require every element of Content to be a *Paragraph. - Content []Block // Content of this item. -} - -// A Paragraph is a paragraph of text. -type Paragraph struct { - Text []Text -} - -func (*Paragraph) block() {} - -// A Code is a preformatted code block. -type Code struct { - // Text is the preformatted text, ending with a newline character. - // It may be multiple lines, each of which ends with a newline character. - // It is never empty, nor does it start or end with a blank line. - Text string -} - -func (*Code) block() {} - -// A Text is text-level content in a doc comment, -// one of [Plain], [Italic], [*Link], or [*DocLink]. -type Text interface { - text() -} - -// A Plain is a string rendered as plain text (not italicized). -type Plain string - -func (Plain) text() {} - -// An Italic is a string rendered as italicized text. -type Italic string - -func (Italic) text() {} - -// A Link is a link to a specific URL. -type Link struct { - Auto bool // is this an automatic (implicit) link of a literal URL? - Text []Text // text of link - URL string // target URL of link -} - -func (*Link) text() {} - -// A DocLink is a link to documentation for a Go package or symbol. -type DocLink struct { - Text []Text // text of link - - // ImportPath, Recv, and Name identify the Go package or symbol - // that is the link target. The potential combinations of - // non-empty fields are: - // - ImportPath: a link to another package - // - ImportPath, Name: a link to a const, func, type, or var in another package - // - ImportPath, Recv, Name: a link to a method in another package - // - Name: a link to a const, func, type, or var in this package - // - Recv, Name: a link to a method in this package - ImportPath string // import path - Recv string // receiver type, without any pointer star, for methods - Name string // const, func, type, var, or method name -} - -func (*DocLink) text() {} - -// A Parser is a doc comment parser. -// The fields in the struct can be filled in before calling Parse -// in order to customize the details of the parsing process. -type Parser struct { - // Words is a map of Go identifier words that - // should be italicized and potentially linked. - // If Words[w] is the empty string, then the word w - // is only italicized. Otherwise it is linked, using - // Words[w] as the link target. - // Words corresponds to the [go/doc.ToHTML] words parameter. - Words map[string]string - - // LookupPackage resolves a package name to an import path. - // - // If LookupPackage(name) returns ok == true, then [name] - // (or [name.Sym] or [name.Sym.Method]) - // is considered a documentation link to importPath's package docs. - // It is valid to return "", true, in which case name is considered - // to refer to the current package. - // - // If LookupPackage(name) returns ok == false, - // then [name] (or [name.Sym] or [name.Sym.Method]) - // will not be considered a documentation link, - // except in the case where name is the full (but single-element) import path - // of a package in the standard library, such as in [math] or [io.Reader]. - // LookupPackage is still called for such names, - // in order to permit references to imports of other packages - // with the same package names. - // - // Setting LookupPackage to nil is equivalent to setting it to - // a function that always returns "", false. - LookupPackage func(name string) (importPath string, ok bool) - - // LookupSym reports whether a symbol name or method name - // exists in the current package. - // - // If LookupSym("", "Name") returns true, then [Name] - // is considered a documentation link for a const, func, type, or var. - // - // Similarly, if LookupSym("Recv", "Name") returns true, - // then [Recv.Name] is considered a documentation link for - // type Recv's method Name. - // - // Setting LookupSym to nil is equivalent to setting it to a function - // that always returns false. - LookupSym func(recv, name string) (ok bool) -} - -// parseDoc is parsing state for a single doc comment. -type parseDoc struct { - *Parser - *Doc - links map[string]*LinkDef - lines []string - lookupSym func(recv, name string) bool -} - -// lookupPkg is called to look up the pkg in [pkg], [pkg.Name], and [pkg.Name.Recv]. -// If pkg has a slash, it is assumed to be the full import path and is returned with ok = true. -// -// Otherwise, pkg is probably a simple package name like "rand" (not "crypto/rand" or "math/rand"). -// d.LookupPackage provides a way for the caller to allow resolving such names with reference -// to the imports in the surrounding package. -// -// There is one collision between these two cases: single-element standard library names -// like "math" are full import paths but don't contain slashes. We let d.LookupPackage have -// the first chance to resolve it, in case there's a different package imported as math, -// and otherwise we refer to a built-in list of single-element standard library package names. -func (d *parseDoc) lookupPkg(pkg string) (importPath string, ok bool) { - if strings.Contains(pkg, "/") { // assume a full import path - if validImportPath(pkg) { - return pkg, true - } - return "", false - } - if d.LookupPackage != nil { - // Give LookupPackage a chance. - if path, ok := d.LookupPackage(pkg); ok { - return path, true - } - } - return DefaultLookupPackage(pkg) -} - -func isStdPkg(path string) bool { - // TODO(rsc): Use sort.Find once we don't have to worry about - // copying this code into older Go environments. - i := sort.Search(len(stdPkgs), func(i int) bool { return stdPkgs[i] >= path }) - return i < len(stdPkgs) && stdPkgs[i] == path -} - -// DefaultLookupPackage is the default package lookup -// function, used when [Parser].LookupPackage is nil. -// It recognizes names of the packages from the standard -// library with single-element import paths, such as math, -// which would otherwise be impossible to name. -// -// Note that the go/doc package provides a more sophisticated -// lookup based on the imports used in the current package. -func DefaultLookupPackage(name string) (importPath string, ok bool) { - if isStdPkg(name) { - return name, true - } - return "", false -} - -// Parse parses the doc comment text and returns the *Doc form. -// Comment markers (/* // and */) in the text must have already been removed. -func (p *Parser) Parse(text string) *Doc { - lines := unindent(strings.Split(text, "\n")) - d := &parseDoc{ - Parser: p, - Doc: new(Doc), - links: make(map[string]*LinkDef), - lines: lines, - lookupSym: func(recv, name string) bool { return false }, - } - if p.LookupSym != nil { - d.lookupSym = p.LookupSym - } - - // First pass: break into block structure and collect known links. - // The text is all recorded as Plain for now. - var prev span - for _, s := range parseSpans(lines) { - var b Block - switch s.kind { - default: - panic("go/doc/comment: internal error: unknown span kind") - case spanList: - b = d.list(lines[s.start:s.end], prev.end < s.start) - case spanCode: - b = d.code(lines[s.start:s.end]) - case spanOldHeading: - b = d.oldHeading(lines[s.start]) - case spanHeading: - b = d.heading(lines[s.start]) - case spanPara: - b = d.paragraph(lines[s.start:s.end]) - } - if b != nil { - d.Content = append(d.Content, b) - } - prev = s - } - - // Second pass: interpret all the Plain text now that we know the links. - for _, b := range d.Content { - switch b := b.(type) { - case *Paragraph: - b.Text = d.parseLinkedText(string(b.Text[0].(Plain))) - case *List: - for _, i := range b.Items { - for _, c := range i.Content { - p := c.(*Paragraph) - p.Text = d.parseLinkedText(string(p.Text[0].(Plain))) - } - } - } - } - - return d.Doc -} - -// A span represents a single span of comment lines (lines[start:end]) -// of an identified kind (code, heading, paragraph, and so on). -type span struct { - start int - end int - kind spanKind -} - -// A spanKind describes the kind of span. -type spanKind int - -const ( - _ spanKind = iota - spanCode - spanHeading - spanList - spanOldHeading - spanPara -) - -func parseSpans(lines []string) []span { - var spans []span - - // The loop may process a line twice: once as unindented - // and again forced indented. So the maximum expected - // number of iterations is 2*len(lines). The repeating logic - // can be subtle, though, and to protect against introduction - // of infinite loops in future changes, we watch to see that - // we are not looping too much. A panic is better than a - // quiet infinite loop. - watchdog := 2 * len(lines) - - i := 0 - forceIndent := 0 -Spans: - for { - // Skip blank lines. - for i < len(lines) && lines[i] == "" { - i++ - } - if i >= len(lines) { - break - } - if watchdog--; watchdog < 0 { - panic("go/doc/comment: internal error: not making progress") - } - - var kind spanKind - start := i - end := i - if i < forceIndent || indented(lines[i]) { - // Indented (or force indented). - // Ends before next unindented. (Blank lines are OK.) - // If this is an unindented list that we are heuristically treating as indented, - // then accept unindented list item lines up to the first blank lines. - // The heuristic is disabled at blank lines to contain its effect - // to non-gofmt'ed sections of the comment. - unindentedListOK := isList(lines[i]) && i < forceIndent - i++ - for i < len(lines) && (lines[i] == "" || i < forceIndent || indented(lines[i]) || (unindentedListOK && isList(lines[i]))) { - if lines[i] == "" { - unindentedListOK = false - } - i++ - } - - // Drop trailing blank lines. - end = i - for end > start && lines[end-1] == "" { - end-- - } - - // If indented lines are followed (without a blank line) - // by an unindented line ending in a brace, - // take that one line too. This fixes the common mistake - // of pasting in something like - // - // func main() { - // fmt.Println("hello, world") - // } - // - // and forgetting to indent it. - // The heuristic will never trigger on a gofmt'ed comment, - // because any gofmt'ed code block or list would be - // followed by a blank line or end of comment. - if end < len(lines) && strings.HasPrefix(lines[end], "}") { - end++ - } - - if isList(lines[start]) { - kind = spanList - } else { - kind = spanCode - } - } else { - // Unindented. Ends at next blank or indented line. - i++ - for i < len(lines) && lines[i] != "" && !indented(lines[i]) { - i++ - } - end = i - - // If unindented lines are followed (without a blank line) - // by an indented line that would start a code block, - // check whether the final unindented lines - // should be left for the indented section. - // This can happen for the common mistakes of - // unindented code or unindented lists. - // The heuristic will never trigger on a gofmt'ed comment, - // because any gofmt'ed code block would have a blank line - // preceding it after the unindented lines. - if i < len(lines) && lines[i] != "" && !isList(lines[i]) { - switch { - case isList(lines[i-1]): - // If the final unindented line looks like a list item, - // this may be the first indented line wrap of - // a mistakenly unindented list. - // Leave all the unindented list items. - forceIndent = end - end-- - for end > start && isList(lines[end-1]) { - end-- - } - - case strings.HasSuffix(lines[i-1], "{") || strings.HasSuffix(lines[i-1], `\`): - // If the final unindented line ended in { or \ - // it is probably the start of a misindented code block. - // Give the user a single line fix. - // Often that's enough; if not, the user can fix the others themselves. - forceIndent = end - end-- - } - - if start == end && forceIndent > start { - i = start - continue Spans - } - } - - // Span is either paragraph or heading. - if end-start == 1 && isHeading(lines[start]) { - kind = spanHeading - } else if end-start == 1 && isOldHeading(lines[start], lines, start) { - kind = spanOldHeading - } else { - kind = spanPara - } - } - - spans = append(spans, span{start, end, kind}) - i = end - } - - return spans -} - -// indented reports whether line is indented -// (starts with a leading space or tab). -func indented(line string) bool { - return line != "" && (line[0] == ' ' || line[0] == '\t') -} - -// unindent removes any common space/tab prefix -// from each line in lines, returning a copy of lines in which -// those prefixes have been trimmed from each line. -// It also replaces any lines containing only spaces with blank lines (empty strings). -func unindent(lines []string) []string { - // Trim leading and trailing blank lines. - for len(lines) > 0 && isBlank(lines[0]) { - lines = lines[1:] - } - for len(lines) > 0 && isBlank(lines[len(lines)-1]) { - lines = lines[:len(lines)-1] - } - if len(lines) == 0 { - return nil - } - - // Compute and remove common indentation. - prefix := leadingSpace(lines[0]) - for _, line := range lines[1:] { - if !isBlank(line) { - prefix = commonPrefix(prefix, leadingSpace(line)) - } - } - - out := make([]string, len(lines)) - for i, line := range lines { - line = strings.TrimPrefix(line, prefix) - if strings.TrimSpace(line) == "" { - line = "" - } - out[i] = line - } - for len(out) > 0 && out[0] == "" { - out = out[1:] - } - for len(out) > 0 && out[len(out)-1] == "" { - out = out[:len(out)-1] - } - return out -} - -// isBlank reports whether s is a blank line. -func isBlank(s string) bool { - return len(s) == 0 || (len(s) == 1 && s[0] == '\n') -} - -// commonPrefix returns the longest common prefix of a and b. -func commonPrefix(a, b string) string { - i := 0 - for i < len(a) && i < len(b) && a[i] == b[i] { - i++ - } - return a[0:i] -} - -// leadingSpace returns the longest prefix of s consisting of spaces and tabs. -func leadingSpace(s string) string { - i := 0 - for i < len(s) && (s[i] == ' ' || s[i] == '\t') { - i++ - } - return s[:i] -} - -// isOldHeading reports whether line is an old-style section heading. -// line is all[off]. -func isOldHeading(line string, all []string, off int) bool { - if off <= 0 || all[off-1] != "" || off+2 >= len(all) || all[off+1] != "" || leadingSpace(all[off+2]) != "" { - return false - } - - line = strings.TrimSpace(line) - - // a heading must start with an uppercase letter - r, _ := utf8.DecodeRuneInString(line) - if !unicode.IsLetter(r) || !unicode.IsUpper(r) { - return false - } - - // it must end in a letter or digit: - r, _ = utf8.DecodeLastRuneInString(line) - if !unicode.IsLetter(r) && !unicode.IsDigit(r) { - return false - } - - // exclude lines with illegal characters. we allow "()," - if strings.ContainsAny(line, ";:!?+*/=[]{}_^°&§~%#@<\">\\") { - return false - } - - // allow "'" for possessive "'s" only - for b := line; ; { - var ok bool - if _, b, ok = stringsCut(b, "'"); !ok { - break - } - if b != "s" && !strings.HasPrefix(b, "s ") { - return false // ' not followed by s and then end-of-word - } - } - - // allow "." when followed by non-space - for b := line; ; { - var ok bool - if _, b, ok = stringsCut(b, "."); !ok { - break - } - if b == "" || strings.HasPrefix(b, " ") { - return false // not followed by non-space - } - } - - return true -} - -// oldHeading returns the *Heading for the given old-style section heading line. -func (d *parseDoc) oldHeading(line string) Block { - return &Heading{Text: []Text{Plain(strings.TrimSpace(line))}} -} - -// isHeading reports whether line is a new-style section heading. -func isHeading(line string) bool { - return len(line) >= 2 && - line[0] == '#' && - (line[1] == ' ' || line[1] == '\t') && - strings.TrimSpace(line) != "#" -} - -// heading returns the *Heading for the given new-style section heading line. -func (d *parseDoc) heading(line string) Block { - return &Heading{Text: []Text{Plain(strings.TrimSpace(line[1:]))}} -} - -// code returns a code block built from the lines. -func (d *parseDoc) code(lines []string) *Code { - body := unindent(lines) - body = append(body, "") // to get final \n from Join - return &Code{Text: strings.Join(body, "\n")} -} - -// paragraph returns a paragraph block built from the lines. -// If the lines are link definitions, paragraph adds them to d and returns nil. -func (d *parseDoc) paragraph(lines []string) Block { - // Is this a block of known links? Handle. - var defs []*LinkDef - for _, line := range lines { - def, ok := parseLink(line) - if !ok { - goto NoDefs - } - defs = append(defs, def) - } - for _, def := range defs { - d.Links = append(d.Links, def) - if d.links[def.Text] == nil { - d.links[def.Text] = def - } - } - return nil -NoDefs: - - return &Paragraph{Text: []Text{Plain(strings.Join(lines, "\n"))}} -} - -// parseLink parses a single link definition line: -// -// [text]: url -// -// It returns the link definition and whether the line was well formed. -func parseLink(line string) (*LinkDef, bool) { - if line == "" || line[0] != '[' { - return nil, false - } - i := strings.Index(line, "]:") - if i < 0 || i+3 >= len(line) || (line[i+2] != ' ' && line[i+2] != '\t') { - return nil, false - } - - text := line[1:i] - url := strings.TrimSpace(line[i+3:]) - j := strings.Index(url, "://") - if j < 0 || !isScheme(url[:j]) { - return nil, false - } - - // Line has right form and has valid scheme://. - // That's good enough for us - we are not as picky - // about the characters beyond the :// as we are - // when extracting inline URLs from text. - return &LinkDef{Text: text, URL: url}, true -} - -// list returns a list built from the indented lines, -// using forceBlankBefore as the value of the List's ForceBlankBefore field. -func (d *parseDoc) list(lines []string, forceBlankBefore bool) *List { - num, _, _ := listMarker(lines[0]) - var ( - list *List = &List{ForceBlankBefore: forceBlankBefore} - item *ListItem - text []string - ) - flush := func() { - if item != nil { - if para := d.paragraph(text); para != nil { - item.Content = append(item.Content, para) - } - } - text = nil - } - - for _, line := range lines { - if n, after, ok := listMarker(line); ok && (n != "") == (num != "") { - // start new list item - flush() - - item = &ListItem{Number: n} - list.Items = append(list.Items, item) - line = after - } - line = strings.TrimSpace(line) - if line == "" { - list.ForceBlankBetween = true - flush() - continue - } - text = append(text, strings.TrimSpace(line)) - } - flush() - return list -} - -// listMarker parses the line as beginning with a list marker. -// If it can do that, it returns the numeric marker ("" for a bullet list), -// the rest of the line, and ok == true. -// Otherwise, it returns "", "", false. -func listMarker(line string) (num, rest string, ok bool) { - line = strings.TrimSpace(line) - if line == "" { - return "", "", false - } - - // Can we find a marker? - if r, n := utf8.DecodeRuneInString(line); r == '•' || r == '*' || r == '+' || r == '-' { - num, rest = "", line[n:] - } else if '0' <= line[0] && line[0] <= '9' { - n := 1 - for n < len(line) && '0' <= line[n] && line[n] <= '9' { - n++ - } - if n >= len(line) || (line[n] != '.' && line[n] != ')') { - return "", "", false - } - num, rest = line[:n], line[n+1:] - } else { - return "", "", false - } - - if !indented(rest) || strings.TrimSpace(rest) == "" { - return "", "", false - } - - return num, rest, true -} - -// isList reports whether the line is the first line of a list, -// meaning starts with a list marker after any indentation. -// (The caller is responsible for checking the line is indented, as appropriate.) -func isList(line string) bool { - _, _, ok := listMarker(line) - return ok -} - -// parseLinkedText parses text that is allowed to contain explicit links, -// such as [math.Sin] or [Go home page], into a slice of Text items. -// -// A “pkg” is only assumed to be a full import path if it starts with -// a domain name (a path element with a dot) or is one of the packages -// from the standard library (“[os]”, “[encoding/json]”, and so on). -// To avoid problems with maps, generics, and array types, doc links -// must be both preceded and followed by punctuation, spaces, tabs, -// or the start or end of a line. An example problem would be treating -// map[ast.Expr]TypeAndValue as containing a link. -func (d *parseDoc) parseLinkedText(text string) []Text { - var out []Text - wrote := 0 - flush := func(i int) { - if wrote < i { - out = d.parseText(out, text[wrote:i], true) - wrote = i - } - } - - start := -1 - var buf []byte - for i := 0; i < len(text); i++ { - c := text[i] - if c == '\n' || c == '\t' { - c = ' ' - } - switch c { - case '[': - start = i - case ']': - if start >= 0 { - if def, ok := d.links[string(buf)]; ok { - def.Used = true - flush(start) - out = append(out, &Link{ - Text: d.parseText(nil, text[start+1:i], false), - URL: def.URL, - }) - wrote = i + 1 - } else if link, ok := d.docLink(text[start+1:i], text[:start], text[i+1:]); ok { - flush(start) - link.Text = d.parseText(nil, text[start+1:i], false) - out = append(out, link) - wrote = i + 1 - } - } - start = -1 - buf = buf[:0] - } - if start >= 0 && i != start { - buf = append(buf, c) - } - } - - flush(len(text)) - return out -} - -// docLink parses text, which was found inside [ ] brackets, -// as a doc link if possible, returning the DocLink and ok == true -// or else nil, false. -// The before and after strings are the text before the [ and after the ] -// on the same line. Doc links must be preceded and followed by -// punctuation, spaces, tabs, or the start or end of a line. -func (d *parseDoc) docLink(text, before, after string) (link *DocLink, ok bool) { - if before != "" { - r, _ := utf8.DecodeLastRuneInString(before) - if !unicode.IsPunct(r) && r != ' ' && r != '\t' && r != '\n' { - return nil, false - } - } - if after != "" { - r, _ := utf8.DecodeRuneInString(after) - if !unicode.IsPunct(r) && r != ' ' && r != '\t' && r != '\n' { - return nil, false - } - } - if strings.HasPrefix(text, "*") { - text = text[1:] - } - pkg, name, ok := splitDocName(text) - var recv string - if ok { - pkg, recv, _ = splitDocName(pkg) - } - if pkg != "" { - if pkg, ok = d.lookupPkg(pkg); !ok { - return nil, false - } - } else { - if ok = d.lookupSym(recv, name); !ok { - return nil, false - } - } - link = &DocLink{ - ImportPath: pkg, - Recv: recv, - Name: name, - } - return link, true -} - -// If text is of the form before.Name, where Name is a capitalized Go identifier, -// then splitDocName returns before, name, true. -// Otherwise it returns text, "", false. -func splitDocName(text string) (before, name string, foundDot bool) { - i := strings.LastIndex(text, ".") - name = text[i+1:] - if !isName(name) { - return text, "", false - } - if i >= 0 { - before = text[:i] - } - return before, name, true -} - -// parseText parses s as text and returns the result of appending -// those parsed Text elements to out. -// parseText does not handle explicit links like [math.Sin] or [Go home page]: -// those are handled by parseLinkedText. -// If autoLink is true, then parseText recognizes URLs and words from d.Words -// and converts those to links as appropriate. -func (d *parseDoc) parseText(out []Text, s string, autoLink bool) []Text { - var w strings.Builder - wrote := 0 - writeUntil := func(i int) { - w.WriteString(s[wrote:i]) - wrote = i - } - flush := func(i int) { - writeUntil(i) - if w.Len() > 0 { - out = append(out, Plain(w.String())) - w.Reset() - } - } - for i := 0; i < len(s); { - t := s[i:] - if autoLink { - if url, ok := autoURL(t); ok { - flush(i) - // Note: The old comment parser would look up the URL in words - // and replace the target with words[URL] if it was non-empty. - // That would allow creating links that display as one URL but - // when clicked go to a different URL. Not sure what the point - // of that is, so we're not doing that lookup here. - out = append(out, &Link{Auto: true, Text: []Text{Plain(url)}, URL: url}) - i += len(url) - wrote = i - continue - } - if id, ok := ident(t); ok { - url, italics := d.Words[id] - if !italics { - i += len(id) - continue - } - flush(i) - if url == "" { - out = append(out, Italic(id)) - } else { - out = append(out, &Link{Auto: true, Text: []Text{Italic(id)}, URL: url}) - } - i += len(id) - wrote = i - continue - } - } - switch { - case strings.HasPrefix(t, "``"): - if len(t) >= 3 && t[2] == '`' { - // Do not convert `` inside ```, in case people are mistakenly writing Markdown. - i += 3 - for i < len(t) && t[i] == '`' { - i++ - } - break - } - writeUntil(i) - w.WriteRune('“') - i += 2 - wrote = i - case strings.HasPrefix(t, "''"): - writeUntil(i) - w.WriteRune('”') - i += 2 - wrote = i - default: - i++ - } - } - flush(len(s)) - return out -} - -// autoURL checks whether s begins with a URL that should be hyperlinked. -// If so, it returns the URL, which is a prefix of s, and ok == true. -// Otherwise it returns "", false. -// The caller should skip over the first len(url) bytes of s -// before further processing. -func autoURL(s string) (url string, ok bool) { - // Find the ://. Fast path to pick off non-URL, - // since we call this at every position in the string. - // The shortest possible URL is ftp://x, 7 bytes. - var i int - switch { - case len(s) < 7: - return "", false - case s[3] == ':': - i = 3 - case s[4] == ':': - i = 4 - case s[5] == ':': - i = 5 - case s[6] == ':': - i = 6 - default: - return "", false - } - if i+3 > len(s) || s[i:i+3] != "://" { - return "", false - } - - // Check valid scheme. - if !isScheme(s[:i]) { - return "", false - } - - // Scan host part. Must have at least one byte, - // and must start and end in non-punctuation. - i += 3 - if i >= len(s) || !isHost(s[i]) || isPunct(s[i]) { - return "", false - } - i++ - end := i - for i < len(s) && isHost(s[i]) { - if !isPunct(s[i]) { - end = i + 1 - } - i++ - } - i = end - - // At this point we are definitely returning a URL (scheme://host). - // We just have to find the longest path we can add to it. - // Heuristics abound. - // We allow parens, braces, and brackets, - // but only if they match (#5043, #22285). - // We allow .,:;?! in the path but not at the end, - // to avoid end-of-sentence punctuation (#18139, #16565). - stk := []byte{} - end = i -Path: - for ; i < len(s); i++ { - if isPunct(s[i]) { - continue - } - if !isPath(s[i]) { - break - } - switch s[i] { - case '(': - stk = append(stk, ')') - case '{': - stk = append(stk, '}') - case '[': - stk = append(stk, ']') - case ')', '}', ']': - if len(stk) == 0 || stk[len(stk)-1] != s[i] { - break Path - } - stk = stk[:len(stk)-1] - } - if len(stk) == 0 { - end = i + 1 - } - } - - return s[:end], true -} - -// isScheme reports whether s is a recognized URL scheme. -// Note that if strings of new length (beyond 3-7) -// are added here, the fast path at the top of autoURL will need updating. -func isScheme(s string) bool { - switch s { - case "file", - "ftp", - "gopher", - "http", - "https", - "mailto", - "nntp": - return true - } - return false -} - -// isHost reports whether c is a byte that can appear in a URL host, -// like www.example.com or user@[::1]:8080 -func isHost(c byte) bool { - // mask is a 128-bit bitmap with 1s for allowed bytes, - // so that the byte c can be tested with a shift and an and. - // If c > 128, then 1<>64)) != 0 -} - -// isPunct reports whether c is a punctuation byte that can appear -// inside a path but not at the end. -func isPunct(c byte) bool { - // mask is a 128-bit bitmap with 1s for allowed bytes, - // so that the byte c can be tested with a shift and an and. - // If c > 128, then 1<>64)) != 0 -} - -// isPath reports whether c is a (non-punctuation) path byte. -func isPath(c byte) bool { - // mask is a 128-bit bitmap with 1s for allowed bytes, - // so that the byte c can be tested with a shift and an and. - // If c > 128, then 1<>64)) != 0 -} - -// isName reports whether s is a capitalized Go identifier (like Name). -func isName(s string) bool { - t, ok := ident(s) - if !ok || t != s { - return false - } - r, _ := utf8.DecodeRuneInString(s) - return unicode.IsUpper(r) -} - -// ident checks whether s begins with a Go identifier. -// If so, it returns the identifier, which is a prefix of s, and ok == true. -// Otherwise it returns "", false. -// The caller should skip over the first len(id) bytes of s -// before further processing. -func ident(s string) (id string, ok bool) { - // Scan [\pL_][\pL_0-9]* - n := 0 - for n < len(s) { - if c := s[n]; c < utf8.RuneSelf { - if isIdentASCII(c) && (n > 0 || c < '0' || c > '9') { - n++ - continue - } - break - } - r, nr := utf8.DecodeRuneInString(s[n:]) - if unicode.IsLetter(r) { - n += nr - continue - } - break - } - return s[:n], n > 0 -} - -// isIdentASCII reports whether c is an ASCII identifier byte. -func isIdentASCII(c byte) bool { - // mask is a 128-bit bitmap with 1s for allowed bytes, - // so that the byte c can be tested with a shift and an and. - // If c > 128, then 1<>64)) != 0 -} - -// validImportPath reports whether path is a valid import path. -// It is a lightly edited copy of golang.org/x/mod/module.CheckImportPath. -func validImportPath(path string) bool { - if !utf8.ValidString(path) { - return false - } - if path == "" { - return false - } - if path[0] == '-' { - return false - } - if strings.Contains(path, "//") { - return false - } - if path[len(path)-1] == '/' { - return false - } - elemStart := 0 - for i, r := range path { - if r == '/' { - if !validImportPathElem(path[elemStart:i]) { - return false - } - elemStart = i + 1 - } - } - return validImportPathElem(path[elemStart:]) -} - -func validImportPathElem(elem string) bool { - if elem == "" || elem[0] == '.' || elem[len(elem)-1] == '.' { - return false - } - for i := 0; i < len(elem); i++ { - if !importPathOK(elem[i]) { - return false - } - } - return true -} - -func importPathOK(c byte) bool { - // mask is a 128-bit bitmap with 1s for allowed bytes, - // so that the byte c can be tested with a shift and an and. - // If c > 128, then 1<>64)) != 0 -} - -func stringsCut(s, sep string) (before, after string, found bool) { - if i := strings.Index(s, sep); i >= 0 { - return s[:i], s[i+len(sep):], true - } - return s, "", false -} diff --git a/internal/backport/go/doc/comment/parse_test.go b/internal/backport/go/doc/comment/parse_test.go deleted file mode 100644 index bce733eaae..0000000000 --- a/internal/backport/go/doc/comment/parse_test.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2022 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 comment - -import "testing" - -// See https://golang.org/issue/52353 -func Test52353(t *testing.T) { - ident("𫕐ﯯ") -} diff --git a/internal/backport/go/doc/comment/print.go b/internal/backport/go/doc/comment/print.go deleted file mode 100644 index 4a57b89ee3..0000000000 --- a/internal/backport/go/doc/comment/print.go +++ /dev/null @@ -1,290 +0,0 @@ -// Copyright 2022 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 comment - -import ( - "bytes" - "fmt" - "strings" -) - -// A Printer is a doc comment printer. -// The fields in the struct can be filled in before calling -// any of the printing methods -// in order to customize the details of the printing process. -type Printer struct { - // HeadingLevel is the nesting level used for - // HTML and Markdown headings. - // If HeadingLevel is zero, it defaults to level 3, - // meaning to use

and ###. - HeadingLevel int - - // HeadingID is a function that computes the heading ID - // (anchor tag) to use for the heading h when generating - // HTML and Markdown. If HeadingID returns an empty string, - // then the heading ID is omitted. - // If HeadingID is nil, h.DefaultID is used. - HeadingID func(h *Heading) string - - // DocLinkURL is a function that computes the URL for the given DocLink. - // If DocLinkURL is nil, then link.DefaultURL(p.DocLinkBaseURL) is used. - DocLinkURL func(link *DocLink) string - - // DocLinkBaseURL is used when DocLinkURL is nil, - // passed to [DocLink.DefaultURL] to construct a DocLink's URL. - // See that method's documentation for details. - DocLinkBaseURL string - - // TextPrefix is a prefix to print at the start of every line - // when generating text output using the Text method. - TextPrefix string - - // TextCodePrefix is the prefix to print at the start of each - // preformatted (code block) line when generating text output, - // instead of (not in addition to) TextPrefix. - // If TextCodePrefix is the empty string, it defaults to TextPrefix+"\t". - TextCodePrefix string - - // TextWidth is the maximum width text line to generate, - // measured in Unicode code points, - // excluding TextPrefix and the newline character. - // If TextWidth is zero, it defaults to 80 minus the number of code points in TextPrefix. - // If TextWidth is negative, there is no limit. - TextWidth int -} - -func (p *Printer) headingLevel() int { - if p.HeadingLevel <= 0 { - return 3 - } - return p.HeadingLevel -} - -func (p *Printer) headingID(h *Heading) string { - if p.HeadingID == nil { - return h.DefaultID() - } - return p.HeadingID(h) -} - -func (p *Printer) docLinkURL(link *DocLink) string { - if p.DocLinkURL != nil { - return p.DocLinkURL(link) - } - return link.DefaultURL(p.DocLinkBaseURL) -} - -// DefaultURL constructs and returns the documentation URL for l, -// using baseURL as a prefix for links to other packages. -// -// The possible forms returned by DefaultURL are: -// - baseURL/ImportPath, for a link to another package -// - baseURL/ImportPath#Name, for a link to a const, func, type, or var in another package -// - baseURL/ImportPath#Recv.Name, for a link to a method in another package -// - #Name, for a link to a const, func, type, or var in this package -// - #Recv.Name, for a link to a method in this package -// -// If baseURL ends in a trailing slash, then DefaultURL inserts -// a slash between ImportPath and # in the anchored forms. -// For example, here are some baseURL values and URLs they can generate: -// -// "/pkg/" → "/pkg/math/#Sqrt" -// "/pkg" → "/pkg/math#Sqrt" -// "/" → "/math/#Sqrt" -// "" → "/math#Sqrt" -func (l *DocLink) DefaultURL(baseURL string) string { - if l.ImportPath != "" { - slash := "" - if strings.HasSuffix(baseURL, "/") { - slash = "/" - } else { - baseURL += "/" - } - switch { - case l.Name == "": - return baseURL + l.ImportPath + slash - case l.Recv != "": - return baseURL + l.ImportPath + slash + "#" + l.Recv + "." + l.Name - default: - return baseURL + l.ImportPath + slash + "#" + l.Name - } - } - if l.Recv != "" { - return "#" + l.Recv + "." + l.Name - } - return "#" + l.Name -} - -// DefaultID returns the default anchor ID for the heading h. -// -// The default anchor ID is constructed by converting every -// rune that is not alphanumeric ASCII to an underscore -// and then adding the prefix “hdr-”. -// For example, if the heading text is “Go Doc Comments”, -// the default ID is “hdr-Go_Doc_Comments”. -func (h *Heading) DefaultID() string { - // Note: The “hdr-” prefix is important to avoid DOM clobbering attacks. - // See https://pkg.go.dev/github.com/google/safehtml#Identifier. - var out strings.Builder - var p textPrinter - p.oneLongLine(&out, h.Text) - s := strings.TrimSpace(out.String()) - if s == "" { - return "" - } - out.Reset() - out.WriteString("hdr-") - for _, r := range s { - if r < 0x80 && isIdentASCII(byte(r)) { - out.WriteByte(byte(r)) - } else { - out.WriteByte('_') - } - } - return out.String() -} - -type commentPrinter struct { - *Printer - headingPrefix string - needDoc map[string]bool -} - -// Comment returns the standard Go formatting of the Doc, -// without any comment markers. -func (p *Printer) Comment(d *Doc) []byte { - cp := &commentPrinter{Printer: p} - var out bytes.Buffer - for i, x := range d.Content { - if i > 0 && blankBefore(x) { - out.WriteString("\n") - } - cp.block(&out, x) - } - - // Print one block containing all the link definitions that were used, - // and then a second block containing all the unused ones. - // This makes it easy to clean up the unused ones: gofmt and - // delete the final block. And it's a nice visual signal without - // affecting the way the comment formats for users. - for i := 0; i < 2; i++ { - used := i == 0 - first := true - for _, def := range d.Links { - if def.Used == used { - if first { - out.WriteString("\n") - first = false - } - out.WriteString("[") - out.WriteString(def.Text) - out.WriteString("]: ") - out.WriteString(def.URL) - out.WriteString("\n") - } - } - } - - return out.Bytes() -} - -// blankBefore reports whether the block x requires a blank line before it. -// All blocks do, except for Lists that return false from x.BlankBefore(). -func blankBefore(x Block) bool { - if x, ok := x.(*List); ok { - return x.BlankBefore() - } - return true -} - -// block prints the block x to out. -func (p *commentPrinter) block(out *bytes.Buffer, x Block) { - switch x := x.(type) { - default: - fmt.Fprintf(out, "?%T", x) - - case *Paragraph: - p.text(out, "", x.Text) - out.WriteString("\n") - - case *Heading: - out.WriteString("# ") - p.text(out, "", x.Text) - out.WriteString("\n") - - case *Code: - md := x.Text - for md != "" { - var line string - line, md, _ = stringsCut(md, "\n") - if line != "" { - out.WriteString("\t") - out.WriteString(line) - } - out.WriteString("\n") - } - - case *List: - loose := x.BlankBetween() - for i, item := range x.Items { - if i > 0 && loose { - out.WriteString("\n") - } - out.WriteString(" ") - if item.Number == "" { - out.WriteString(" - ") - } else { - out.WriteString(item.Number) - out.WriteString(". ") - } - for i, blk := range item.Content { - const fourSpace = " " - if i > 0 { - out.WriteString("\n" + fourSpace) - } - p.text(out, fourSpace, blk.(*Paragraph).Text) - out.WriteString("\n") - } - } - } -} - -// text prints the text sequence x to out. -func (p *commentPrinter) text(out *bytes.Buffer, indent string, x []Text) { - for _, t := range x { - switch t := t.(type) { - case Plain: - p.indent(out, indent, string(t)) - case Italic: - p.indent(out, indent, string(t)) - case *Link: - if t.Auto { - p.text(out, indent, t.Text) - } else { - out.WriteString("[") - p.text(out, indent, t.Text) - out.WriteString("]") - } - case *DocLink: - out.WriteString("[") - p.text(out, indent, t.Text) - out.WriteString("]") - } - } -} - -// indent prints s to out, indenting with the indent string -// after each newline in s. -func (p *commentPrinter) indent(out *bytes.Buffer, indent, s string) { - for s != "" { - line, rest, ok := stringsCut(s, "\n") - out.WriteString(line) - if ok { - out.WriteString("\n") - out.WriteString(indent) - } - s = rest - } -} diff --git a/internal/backport/go/doc/comment/std.go b/internal/backport/go/doc/comment/std.go deleted file mode 100644 index 71f15f47b1..0000000000 --- a/internal/backport/go/doc/comment/std.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2022 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. - -// Code generated by 'go generate' DO NOT EDIT. -//go:generate ./mkstd.sh - -package comment - -var stdPkgs = []string{ - "bufio", - "bytes", - "context", - "crypto", - "embed", - "encoding", - "errors", - "expvar", - "flag", - "fmt", - "hash", - "html", - "image", - "io", - "log", - "math", - "mime", - "net", - "os", - "path", - "plugin", - "reflect", - "regexp", - "runtime", - "sort", - "strconv", - "strings", - "sync", - "syscall", - "testing", - "time", - "unicode", - "unsafe", -} diff --git a/internal/backport/go/doc/comment/std_test.go b/internal/backport/go/doc/comment/std_test.go deleted file mode 100644 index 7f3aba8f7e..0000000000 --- a/internal/backport/go/doc/comment/std_test.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2022 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 comment - -import ( - "os/exec" - "sort" - "strings" - "testing" - - "golang.org/x/website/internal/backport/diff" -) - -func TestStd(t *testing.T) { - out, err := exec.Command("go", "list", "std").CombinedOutput() - if err != nil { - t.Fatalf("%v\n%s", err, out) - } - - var list []string - for _, pkg := range strings.Fields(string(out)) { - if !strings.Contains(pkg, "/") { - list = append(list, pkg) - } - } - sort.Strings(list) - - have := strings.Join(stdPkgs, "\n") + "\n" - want := strings.Join(list, "\n") + "\n" - if have != want { - t.Errorf("stdPkgs is out of date: regenerate with 'go generate'\n%s", diff.Diff("stdPkgs", []byte(have), "want", []byte(want))) - } -} diff --git a/internal/backport/go/doc/comment/testdata/README.md b/internal/backport/go/doc/comment/testdata/README.md deleted file mode 100644 index d6f2c54960..0000000000 --- a/internal/backport/go/doc/comment/testdata/README.md +++ /dev/null @@ -1,42 +0,0 @@ -This directory contains test files (*.txt) for the comment parser. - -The files are in [txtar format](https://pkg.go.dev/golang.org/x/tools/txtar). -Consider this example: - - -- input -- - Hello. - -- gofmt -- - Hello. - -- html -- -

Hello. - -- markdown -- - Hello. - -- text -- - Hello. - -Each `-- name --` line introduces a new file with the given name. -The file named “input” must be first and contains the input to -[comment.Parser](https://pkg.go.dev/go/doc/comment/#Parser). - -The remaining files contain the expected output for the named format generated by -[comment.Printer](https://pkg.go.dev/go/doc/comment/#Printer): -“gofmt” for Printer.Comment (Go comment format, as used by gofmt), -“html” for Printer.HTML, “markdown” for Printer.Markdown, and “text” for Printer.Text. -The format can also be “dump” for a textual dump of the raw data structures. - -The text before the `-- input --` line, if present, is JSON to be unmarshalled -to initialize a comment.Printer. For example, this test case sets the Printer's -TextWidth field to 20: - - {"TextWidth": 20} - -- input -- - Package gob manages streams of gobs - binary values exchanged between an - Encoder (transmitter) and a Decoder (receiver). - -- text -- - Package gob - manages streams - of gobs - binary - values exchanged - between an Encoder - (transmitter) and a - Decoder (receiver). diff --git a/internal/backport/go/doc/comment/testdata/blank.txt b/internal/backport/go/doc/comment/testdata/blank.txt deleted file mode 100644 index 9049fde76e..0000000000 --- a/internal/backport/go/doc/comment/testdata/blank.txt +++ /dev/null @@ -1,12 +0,0 @@ --- input -- - $ - Blank line at start and end. - $ --- gofmt -- -Blank line at start and end. --- text -- -Blank line at start and end. --- markdown -- -Blank line at start and end. --- html -- -

Blank line at start and end. diff --git a/internal/backport/go/doc/comment/testdata/code.txt b/internal/backport/go/doc/comment/testdata/code.txt deleted file mode 100644 index 06b1519574..0000000000 --- a/internal/backport/go/doc/comment/testdata/code.txt +++ /dev/null @@ -1,94 +0,0 @@ --- input -- -Text. - A tab-indented - (no, not eight-space indented) - code block and haiku. -More text. - One space - is - enough - to - start - a - block. -More text. - - Blocks - can - - have - blank - lines. --- gofmt -- -Text. - - A tab-indented - (no, not eight-space indented) - code block and haiku. - -More text. - - One space - is - enough - to - start - a - block. - -More text. - - Blocks - can - - have - blank - lines. --- markdown -- -Text. - - A tab-indented - (no, not eight-space indented) - code block and haiku. - -More text. - - One space - is - enough - to - start - a - block. - -More text. - - Blocks - can - - have - blank - lines. --- html -- -

Text. -

A tab-indented
-(no, not eight-space indented)
-code block and haiku.
-
-

More text. -

One space
- is
-  enough
-   to
-    start
-     a
-      block.
-
-

More text. -

    Blocks
-  can
-
-have
-  blank
-    lines.
-
diff --git a/internal/backport/go/doc/comment/testdata/code2.txt b/internal/backport/go/doc/comment/testdata/code2.txt deleted file mode 100644 index 0810bed41c..0000000000 --- a/internal/backport/go/doc/comment/testdata/code2.txt +++ /dev/null @@ -1,31 +0,0 @@ --- input -- -Text. - - A tab-indented - (no, not eight-space indented) - code block and haiku. - -More text. --- gofmt -- -Text. - - A tab-indented - (no, not eight-space indented) - code block and haiku. - -More text. --- markdown -- -Text. - - A tab-indented - (no, not eight-space indented) - code block and haiku. - -More text. --- html -- -

Text. -

A tab-indented
-(no, not eight-space indented)
-code block and haiku.
-
-

More text. diff --git a/internal/backport/go/doc/comment/testdata/code3.txt b/internal/backport/go/doc/comment/testdata/code3.txt deleted file mode 100644 index 4a96a0e9ab..0000000000 --- a/internal/backport/go/doc/comment/testdata/code3.txt +++ /dev/null @@ -1,33 +0,0 @@ --- input -- -Text. - - $ - A tab-indented - (surrounded by more blank lines) - code block and haiku. - $ - -More text. --- gofmt -- -Text. - - A tab-indented - (surrounded by more blank lines) - code block and haiku. - -More text. --- markdown -- -Text. - - A tab-indented - (surrounded by more blank lines) - code block and haiku. - -More text. --- html -- -

Text. -

A tab-indented
-(surrounded by more blank lines)
-code block and haiku.
-
-

More text. diff --git a/internal/backport/go/doc/comment/testdata/code4.txt b/internal/backport/go/doc/comment/testdata/code4.txt deleted file mode 100644 index f128c9aeff..0000000000 --- a/internal/backport/go/doc/comment/testdata/code4.txt +++ /dev/null @@ -1,38 +0,0 @@ --- input -- -To test, run this command: - go test -more - -Or, to test specific things, run this command: - -go test -more \ - -pkg first/package \ - -pkg second/package \ - -pkg third/package - -Happy testing! --- gofmt -- -To test, run this command: - - go test -more - -Or, to test specific things, run this command: - - go test -more \ - -pkg first/package \ - -pkg second/package \ - -pkg third/package - -Happy testing! --- markdown -- -To test, run this command: - - go test -more - -Or, to test specific things, run this command: - - go test -more \ - -pkg first/package \ - -pkg second/package \ - -pkg third/package - -Happy testing! diff --git a/internal/backport/go/doc/comment/testdata/code5.txt b/internal/backport/go/doc/comment/testdata/code5.txt deleted file mode 100644 index 0e340dd129..0000000000 --- a/internal/backport/go/doc/comment/testdata/code5.txt +++ /dev/null @@ -1,21 +0,0 @@ --- input -- -L1 -L2 -L3 -L4 -L5 -- L6 { - L7 -} -L8 --- gofmt -- -L1 -L2 -L3 -L4 -L5 - - L6 { - L7 - } - -L8 diff --git a/internal/backport/go/doc/comment/testdata/code6.txt b/internal/backport/go/doc/comment/testdata/code6.txt deleted file mode 100644 index d2915d1068..0000000000 --- a/internal/backport/go/doc/comment/testdata/code6.txt +++ /dev/null @@ -1,24 +0,0 @@ --- input -- -Run this program: - -func main() { - fmt.Println("hello, world") -} - -Or this: - -go func() { - fmt.Println("hello, world") -}() --- gofmt -- -Run this program: - - func main() { - fmt.Println("hello, world") - } - -Or this: - - go func() { - fmt.Println("hello, world") - }() diff --git a/internal/backport/go/doc/comment/testdata/crash1.txt b/internal/backport/go/doc/comment/testdata/crash1.txt deleted file mode 100644 index 6bb2f6fdda..0000000000 --- a/internal/backport/go/doc/comment/testdata/crash1.txt +++ /dev/null @@ -1,16 +0,0 @@ --- input -- -[] - -[]: http:// --- gofmt -- -[] - -[]: http:// --- html -- -

--- markdown -- -[](http://) --- text -- - - -[]: http:// diff --git a/internal/backport/go/doc/comment/testdata/doclink.txt b/internal/backport/go/doc/comment/testdata/doclink.txt deleted file mode 100644 index a9323471fd..0000000000 --- a/internal/backport/go/doc/comment/testdata/doclink.txt +++ /dev/null @@ -1,21 +0,0 @@ --- input -- -In this package, see [Doc] and [Parser.Parse]. -There is no [Undef] or [Undef.Method]. -See also the [comment] package, -especially [comment.Doc] and [comment.Parser.Parse]. --- gofmt -- -In this package, see [Doc] and [Parser.Parse]. -There is no [Undef] or [Undef.Method]. -See also the [comment] package, -especially [comment.Doc] and [comment.Parser.Parse]. --- text -- -In this package, see Doc and Parser.Parse. There is no [Undef] or -[Undef.Method]. See also the comment package, especially comment.Doc and -comment.Parser.Parse. --- markdown -- -In this package, see [Doc](#Doc) and [Parser.Parse](#Parser.Parse). There is no \[Undef] or \[Undef.Method]. See also the [comment](/go/doc/comment) package, especially [comment.Doc](/go/doc/comment#Doc) and [comment.Parser.Parse](/go/doc/comment#Parser.Parse). --- html -- -

In this package, see Doc and Parser.Parse. -There is no [Undef] or [Undef.Method]. -See also the comment package, -especially comment.Doc and comment.Parser.Parse. diff --git a/internal/backport/go/doc/comment/testdata/doclink2.txt b/internal/backport/go/doc/comment/testdata/doclink2.txt deleted file mode 100644 index ecd8e4e0bc..0000000000 --- a/internal/backport/go/doc/comment/testdata/doclink2.txt +++ /dev/null @@ -1,8 +0,0 @@ --- input -- -We use [io.Reader] a lot, and also a few map[io.Reader]string. - -Never [io.Reader]int or Slice[io.Reader] though. --- markdown -- -We use [io.Reader](/io#Reader) a lot, and also a few map\[io.Reader]string. - -Never \[io.Reader]int or Slice\[io.Reader] though. diff --git a/internal/backport/go/doc/comment/testdata/doclink3.txt b/internal/backport/go/doc/comment/testdata/doclink3.txt deleted file mode 100644 index 0ccfb3df70..0000000000 --- a/internal/backport/go/doc/comment/testdata/doclink3.txt +++ /dev/null @@ -1,8 +0,0 @@ --- input -- -[encoding/json.Marshal] is a doc link. - -[rot13.Marshal] is not. --- markdown -- -[encoding/json.Marshal](/encoding/json#Marshal) is a doc link. - -\[rot13.Marshal] is not. diff --git a/internal/backport/go/doc/comment/testdata/doclink4.txt b/internal/backport/go/doc/comment/testdata/doclink4.txt deleted file mode 100644 index c7095276bf..0000000000 --- a/internal/backport/go/doc/comment/testdata/doclink4.txt +++ /dev/null @@ -1,7 +0,0 @@ --- input -- -[io] at start of comment. -[io] at start of line. -At end of line: [io] -At end of comment: [io] --- markdown -- -[io](/io) at start of comment. [io](/io) at start of line. At end of line: [io](/io) At end of comment: [io](/io) diff --git a/internal/backport/go/doc/comment/testdata/doclink5.txt b/internal/backport/go/doc/comment/testdata/doclink5.txt deleted file mode 100644 index ac7b3ae100..0000000000 --- a/internal/backport/go/doc/comment/testdata/doclink5.txt +++ /dev/null @@ -1,5 +0,0 @@ -{"DocLinkBaseURL": "https://pkg.go.dev"} --- input -- -[encoding/json.Marshal] is a doc link. --- markdown -- -[encoding/json.Marshal](https://pkg.go.dev/encoding/json#Marshal) is a doc link. diff --git a/internal/backport/go/doc/comment/testdata/doclink6.txt b/internal/backport/go/doc/comment/testdata/doclink6.txt deleted file mode 100644 index 1acd03b616..0000000000 --- a/internal/backport/go/doc/comment/testdata/doclink6.txt +++ /dev/null @@ -1,5 +0,0 @@ -{"DocLinkBaseURL": "https://go.dev/pkg/"} --- input -- -[encoding/json.Marshal] is a doc link, and so is [rsc.io/quote.NonExist]. --- markdown -- -[encoding/json.Marshal](https://go.dev/pkg/encoding/json/#Marshal) is a doc link, and so is [rsc.io/quote.NonExist](https://go.dev/pkg/rsc.io/quote/#NonExist). diff --git a/internal/backport/go/doc/comment/testdata/doclink7.txt b/internal/backport/go/doc/comment/testdata/doclink7.txt deleted file mode 100644 index d34979a385..0000000000 --- a/internal/backport/go/doc/comment/testdata/doclink7.txt +++ /dev/null @@ -1,4 +0,0 @@ --- input -- -You see more [*bytes.Buffer] than [bytes.Buffer]. --- markdown -- -You see more [\*bytes.Buffer](/bytes#Buffer) than [bytes.Buffer](/bytes#Buffer). diff --git a/internal/backport/go/doc/comment/testdata/escape.txt b/internal/backport/go/doc/comment/testdata/escape.txt deleted file mode 100644 index f54663f5c3..0000000000 --- a/internal/backport/go/doc/comment/testdata/escape.txt +++ /dev/null @@ -1,55 +0,0 @@ --- input -- -What the ~!@#$%^&*()_+-=`{}|[]\:";',./<>? - -+ Line - -- Line - -* Line - -999. Line - -## Line --- gofmt -- -What the ~!@#$%^&*()_+-=`{}|[]\:";',./<>? - -+ Line - -- Line - -* Line - -999. Line - -## Line --- text -- -What the ~!@#$%^&*()_+-=`{}|[]\:";',./<>? - -+ Line - -- Line - -* Line - -999. Line - -## Line --- markdown -- -What the ~!@#$%^&\*()\_+-=\`{}|\[]\\:";',./\<>? - -\+ Line - -\- Line - -\* Line - -999\. Line - -\## Line --- html -- -

What the ~!@#$%^&*()_+-=`{}|[]\:";',./<>? -

+ Line -

- Line -

* Line -

999. Line -

## Line diff --git a/internal/backport/go/doc/comment/testdata/head.txt b/internal/backport/go/doc/comment/testdata/head.txt deleted file mode 100644 index b99a8c59f3..0000000000 --- a/internal/backport/go/doc/comment/testdata/head.txt +++ /dev/null @@ -1,92 +0,0 @@ --- input -- -Some text. - -An Old Heading - -Not An Old Heading. - -And some text. - -# A New Heading. - -And some more text. - -# Not a heading, -because text follows it. - -Because text precedes it, -# not a heading. - -## Not a heading either. - --- gofmt -- -Some text. - -# An Old Heading - -Not An Old Heading. - -And some text. - -# A New Heading. - -And some more text. - -# Not a heading, -because text follows it. - -Because text precedes it, -# not a heading. - -## Not a heading either. - --- text -- -Some text. - -# An Old Heading - -Not An Old Heading. - -And some text. - -# A New Heading. - -And some more text. - -# Not a heading, because text follows it. - -Because text precedes it, # not a heading. - -## Not a heading either. - --- markdown -- -Some text. - -### An Old Heading {#hdr-An_Old_Heading} - -Not An Old Heading. - -And some text. - -### A New Heading. {#hdr-A_New_Heading_} - -And some more text. - -\# Not a heading, because text follows it. - -Because text precedes it, # not a heading. - -\## Not a heading either. - --- html -- -

Some text. -

An Old Heading

-

Not An Old Heading. -

And some text. -

A New Heading.

-

And some more text. -

# Not a heading, -because text follows it. -

Because text precedes it, -# not a heading. -

## Not a heading either. diff --git a/internal/backport/go/doc/comment/testdata/head2.txt b/internal/backport/go/doc/comment/testdata/head2.txt deleted file mode 100644 index d3576325e0..0000000000 --- a/internal/backport/go/doc/comment/testdata/head2.txt +++ /dev/null @@ -1,36 +0,0 @@ --- input -- -✦ - -Almost a+heading - -✦ - -Don't be a heading - -✦ - -A.b is a heading - -✦ - -A. b is not a heading - -✦ --- gofmt -- -✦ - -Almost a+heading - -✦ - -Don't be a heading - -✦ - -# A.b is a heading - -✦ - -A. b is not a heading - -✦ diff --git a/internal/backport/go/doc/comment/testdata/head3.txt b/internal/backport/go/doc/comment/testdata/head3.txt deleted file mode 100644 index dbb7cb3ffb..0000000000 --- a/internal/backport/go/doc/comment/testdata/head3.txt +++ /dev/null @@ -1,7 +0,0 @@ -{"HeadingLevel": 5} --- input -- -# Heading --- markdown -- -##### Heading {#hdr-Heading} --- html -- -

Heading
diff --git a/internal/backport/go/doc/comment/testdata/hello.txt b/internal/backport/go/doc/comment/testdata/hello.txt deleted file mode 100644 index fb07f1eb75..0000000000 --- a/internal/backport/go/doc/comment/testdata/hello.txt +++ /dev/null @@ -1,35 +0,0 @@ --- input -- - Hello, - world - - This is - a test. --- dump -- -Doc - Paragraph - Plain - "Hello,\n" - "world" - Paragraph - Plain - "This is\n" - "a test." --- gofmt -- -Hello, -world - -This is -a test. --- html -- -

Hello, -world -

This is -a test. --- markdown -- -Hello, world - -This is a test. --- text -- -Hello, world - -This is a test. diff --git a/internal/backport/go/doc/comment/testdata/link.txt b/internal/backport/go/doc/comment/testdata/link.txt deleted file mode 100644 index 551e3065ce..0000000000 --- a/internal/backport/go/doc/comment/testdata/link.txt +++ /dev/null @@ -1,17 +0,0 @@ --- input -- -The Go home page is https://go.dev/. -It used to be https://golang.org. - --- gofmt -- -The Go home page is https://go.dev/. -It used to be https://golang.org. - --- text -- -The Go home page is https://go.dev/. It used to be https://golang.org. - --- markdown -- -The Go home page is [https://go.dev/](https://go.dev/). It used to be [https://golang.org](https://golang.org). - --- html -- -

The Go home page is https://go.dev/. -It used to be https://golang.org. diff --git a/internal/backport/go/doc/comment/testdata/link2.txt b/internal/backport/go/doc/comment/testdata/link2.txt deleted file mode 100644 index 8637a32f01..0000000000 --- a/internal/backport/go/doc/comment/testdata/link2.txt +++ /dev/null @@ -1,31 +0,0 @@ --- input -- -The Go home page is https://go.dev/. -It used to be https://golang.org. -https:// is not a link. -Nor is https:// -https://☺ is not a link. -https://:80 is not a link. - --- gofmt -- -The Go home page is https://go.dev/. -It used to be https://golang.org. -https:// is not a link. -Nor is https:// -https://☺ is not a link. -https://:80 is not a link. - --- text -- -The Go home page is https://go.dev/. It used to be https://golang.org. https:// -is not a link. Nor is https:// https://☺ is not a link. https://:80 is not a -link. - --- markdown -- -The Go home page is [https://go.dev/](https://go.dev/). It used to be [https://golang.org](https://golang.org). https:// is not a link. Nor is https:// https://☺ is not a link. https://:80 is not a link. - --- html -- -

The Go home page is https://go.dev/. -It used to be https://golang.org. -https:// is not a link. -Nor is https:// -https://☺ is not a link. -https://:80 is not a link. diff --git a/internal/backport/go/doc/comment/testdata/link3.txt b/internal/backport/go/doc/comment/testdata/link3.txt deleted file mode 100644 index 5a115b5cb7..0000000000 --- a/internal/backport/go/doc/comment/testdata/link3.txt +++ /dev/null @@ -1,14 +0,0 @@ --- input -- -Doc text. - -[Go home page]: https://go.dev --- gofmt -- -Doc text. - -[Go home page]: https://go.dev --- text -- -Doc text. --- markdown -- -Doc text. --- html -- -

Doc text. diff --git a/internal/backport/go/doc/comment/testdata/link4.txt b/internal/backport/go/doc/comment/testdata/link4.txt deleted file mode 100644 index 75f194c845..0000000000 --- a/internal/backport/go/doc/comment/testdata/link4.txt +++ /dev/null @@ -1,77 +0,0 @@ --- input -- -These are not links. - -[x - -[x]: - -[x]:https://go.dev - -[x]https://go.dev - -[x]: surprise://go.dev - -[x]: surprise! - -But this is, with a tab (although it's unused). - -[z]: https://go.dev --- gofmt -- -These are not links. - -[x - -[x]: - -[x]:https://go.dev - -[x]https://go.dev - -[x]: surprise://go.dev - -[x]: surprise! - -But this is, with a tab (although it's unused). - -[z]: https://go.dev --- text -- -These are not links. - -[x - -[x]: - -[x]:https://go.dev - -[x]https://go.dev - -[x]: surprise://go.dev - -[x]: surprise! - -But this is, with a tab (although it's unused). --- markdown -- -These are not links. - -\[x - -\[x]: - -\[x]:[https://go.dev](https://go.dev) - -\[x][https://go.dev](https://go.dev) - -\[x]: surprise://go.dev - -\[x]: surprise! - -But this is, with a tab (although it's unused). --- html -- -

These are not links. -

[x -

[x]: -

[x]:https://go.dev -

[x]https://go.dev -

[x]: surprise://go.dev -

[x]: surprise! -

But this is, with a tab (although it's unused). diff --git a/internal/backport/go/doc/comment/testdata/link5.txt b/internal/backport/go/doc/comment/testdata/link5.txt deleted file mode 100644 index b4fb5889f4..0000000000 --- a/internal/backport/go/doc/comment/testdata/link5.txt +++ /dev/null @@ -1,36 +0,0 @@ --- input -- -See the [Go home page] and the [pkg -site]. - -[Go home page]: https://go.dev/ -[pkg site]: https://pkg.go.dev -[Go home page]: https://duplicate.ignored - -They're really great! - --- gofmt -- -See the [Go home page] and the [pkg -site]. - -They're really great! - -[Go home page]: https://go.dev/ -[pkg site]: https://pkg.go.dev - -[Go home page]: https://duplicate.ignored - --- text -- -See the Go home page and the pkg site. - -They're really great! - -[Go home page]: https://go.dev/ -[pkg site]: https://pkg.go.dev --- markdown -- -See the [Go home page](https://go.dev/) and the [pkg site](https://pkg.go.dev). - -They're really great! --- html -- -

See the Go home page and the pkg -site. -

They're really great! diff --git a/internal/backport/go/doc/comment/testdata/link6.txt b/internal/backport/go/doc/comment/testdata/link6.txt deleted file mode 100644 index ff629b4573..0000000000 --- a/internal/backport/go/doc/comment/testdata/link6.txt +++ /dev/null @@ -1,50 +0,0 @@ --- input -- -URLs with punctuation are hard. -We don't want to consume the end-of-sentence punctuation. - -For example, https://en.wikipedia.org/wiki/John_Adams_(miniseries). -And https://example.com/[foo]/bar{. -And https://example.com/(foo)/bar! -And https://example.com/{foo}/bar{. -And https://example.com/)baz{foo}. - -[And https://example.com/]. - --- gofmt -- -URLs with punctuation are hard. -We don't want to consume the end-of-sentence punctuation. - -For example, https://en.wikipedia.org/wiki/John_Adams_(miniseries). -And https://example.com/[foo]/bar{. -And https://example.com/(foo)/bar! -And https://example.com/{foo}/bar{. -And https://example.com/)baz{foo}. - -[And https://example.com/]. - --- text -- -URLs with punctuation are hard. We don't want to consume the end-of-sentence -punctuation. - -For example, https://en.wikipedia.org/wiki/John_Adams_(miniseries). -And https://example.com/[foo]/bar{. And https://example.com/(foo)/bar! And -https://example.com/{foo}/bar{. And https://example.com/)baz{foo}. - -[And https://example.com/]. - --- markdown -- -URLs with punctuation are hard. We don't want to consume the end-of-sentence punctuation. - -For example, [https://en.wikipedia.org/wiki/John\_Adams\_(miniseries)](https://en.wikipedia.org/wiki/John_Adams_(miniseries)). And [https://example.com/\[foo]/bar](https://example.com/[foo]/bar){. And [https://example.com/(foo)/bar](https://example.com/(foo)/bar)! And [https://example.com/{foo}/bar](https://example.com/{foo}/bar){. And [https://example.com/](https://example.com/))baz{foo}. - -\[And [https://example.com/](https://example.com/)]. - --- html -- -

URLs with punctuation are hard. -We don't want to consume the end-of-sentence punctuation. -

For example, https://en.wikipedia.org/wiki/John_Adams_(miniseries). -And https://example.com/[foo]/bar{. -And https://example.com/(foo)/bar! -And https://example.com/{foo}/bar{. -And https://example.com/)baz{foo}. -

[And https://example.com/]. diff --git a/internal/backport/go/doc/comment/testdata/link7.txt b/internal/backport/go/doc/comment/testdata/link7.txt deleted file mode 100644 index 89a8b3170e..0000000000 --- a/internal/backport/go/doc/comment/testdata/link7.txt +++ /dev/null @@ -1,25 +0,0 @@ --- input -- -[math] is a package but this is not a doc link. - -[io] is a doc link. - -[math]: https://example.com --- gofmt -- -[math] is a package but this is not a doc link. - -[io] is a doc link. - -[math]: https://example.com --- text -- -math is a package but this is not a doc link. - -io is a doc link. - -[math]: https://example.com --- markdown -- -[math](https://example.com) is a package but this is not a doc link. - -[io](/io) is a doc link. --- html -- -

math is a package but this is not a doc link. -

io is a doc link. diff --git a/internal/backport/go/doc/comment/testdata/linklist.txt b/internal/backport/go/doc/comment/testdata/linklist.txt deleted file mode 100644 index baf40624b3..0000000000 --- a/internal/backport/go/doc/comment/testdata/linklist.txt +++ /dev/null @@ -1,18 +0,0 @@ -{"DocLinkBaseURL": "https://pkg.go.dev"} --- input -- -Did you know? - - - [encoding/json.Marshal] is a doc link. So is [encoding/json.Unmarshal]. --- text -- -Did you know? - - - encoding/json.Marshal is a doc link. So is encoding/json.Unmarshal. --- markdown -- -Did you know? - - - [encoding/json.Marshal](https://pkg.go.dev/encoding/json#Marshal) is a doc link. So is [encoding/json.Unmarshal](https://pkg.go.dev/encoding/json#Unmarshal). --- html -- -

Did you know? -

diff --git a/internal/backport/go/doc/comment/testdata/linklist2.txt b/internal/backport/go/doc/comment/testdata/linklist2.txt deleted file mode 100644 index 81b306100f..0000000000 --- a/internal/backport/go/doc/comment/testdata/linklist2.txt +++ /dev/null @@ -1,39 +0,0 @@ -{"DocLinkBaseURL": "https://pkg.go.dev"} --- input -- -Did you know? - - - [testing.T] is one doc link. - - So is [testing.M]. - - So is [testing.B]. - This is the same list paragraph. - - There is [testing.PB] in this list item, too! --- text -- -Did you know? - - - testing.T is one doc link. - - - So is testing.M. - - - So is testing.B. This is the same list paragraph. - - There is testing.PB in this list item, too! --- markdown -- -Did you know? - - - [testing.T](https://pkg.go.dev/testing#T) is one doc link. - - - So is [testing.M](https://pkg.go.dev/testing#M). - - - So is [testing.B](https://pkg.go.dev/testing#B). This is the same list paragraph. - - There is [testing.PB](https://pkg.go.dev/testing#PB) in this list item, too! --- html -- -

Did you know? -

diff --git a/internal/backport/go/doc/comment/testdata/linklist3.txt b/internal/backport/go/doc/comment/testdata/linklist3.txt deleted file mode 100644 index 701a54ecff..0000000000 --- a/internal/backport/go/doc/comment/testdata/linklist3.txt +++ /dev/null @@ -1,31 +0,0 @@ -{"DocLinkBaseURL": "https://pkg.go.dev"} --- input -- -Cool things: - - - Foo - - [Go] - - Bar - -[Go]: https://go.dev/ --- text -- -Cool things: - - - Foo - - Go - - Bar - -[Go]: https://go.dev/ --- markdown -- -Cool things: - - - Foo - - [Go](https://go.dev/) - - Bar - --- html -- -

Cool things: -

    -
  • Foo -
  • Go -
  • Bar -
diff --git a/internal/backport/go/doc/comment/testdata/linklist4.txt b/internal/backport/go/doc/comment/testdata/linklist4.txt deleted file mode 100644 index db39ec4ee1..0000000000 --- a/internal/backport/go/doc/comment/testdata/linklist4.txt +++ /dev/null @@ -1,36 +0,0 @@ -{"DocLinkBaseURL": "https://pkg.go.dev"} --- input -- -Cool things: - - - Foo - - [Go] is great - - [Go]: https://go.dev/ - - Bar - --- text -- -Cool things: - - - Foo - - - Go is great - - - Bar - -[Go]: https://go.dev/ --- markdown -- -Cool things: - - - Foo - - - [Go](https://go.dev/) is great - - - Bar - --- html -- -

Cool things: -

    -
  • Foo -

  • Go is great -

  • Bar -

diff --git a/internal/backport/go/doc/comment/testdata/list.txt b/internal/backport/go/doc/comment/testdata/list.txt deleted file mode 100644 index 455782f864..0000000000 --- a/internal/backport/go/doc/comment/testdata/list.txt +++ /dev/null @@ -1,48 +0,0 @@ --- input -- -Text. -- Not a list. - - Here is the list. - • Using multiple bullets. - * Indentation does not matter. - + Lots of bullets. -More text. - --- gofmt -- -Text. -- Not a list. - - Here is the list. - - Using multiple bullets. - - Indentation does not matter. - - Lots of bullets. - -More text. - --- text -- -Text. - Not a list. - - Here is the list. - - Using multiple bullets. - - Indentation does not matter. - - Lots of bullets. - -More text. - --- markdown -- -Text. - Not a list. - - - Here is the list. - - Using multiple bullets. - - Indentation does not matter. - - Lots of bullets. - -More text. - --- html -- -

Text. -- Not a list. -

    -
  • Here is the list. -
  • Using multiple bullets. -
  • Indentation does not matter. -
  • Lots of bullets. -
-

More text. diff --git a/internal/backport/go/doc/comment/testdata/list10.txt b/internal/backport/go/doc/comment/testdata/list10.txt deleted file mode 100644 index 9c49083456..0000000000 --- a/internal/backport/go/doc/comment/testdata/list10.txt +++ /dev/null @@ -1,13 +0,0 @@ --- input -- - - 1. This list - 2. Starts the comment - 3. And also has a blank line before it. - -All of which is a little weird. --- gofmt -- - 1. This list - 2. Starts the comment - 3. And also has a blank line before it. - -All of which is a little weird. diff --git a/internal/backport/go/doc/comment/testdata/list2.txt b/internal/backport/go/doc/comment/testdata/list2.txt deleted file mode 100644 index c390b3d59a..0000000000 --- a/internal/backport/go/doc/comment/testdata/list2.txt +++ /dev/null @@ -1,57 +0,0 @@ --- input -- -Text. - 1. Uno - 2) Dos - 3. Tres - 5. Cinco - 7. Siete - 11. Once - 12. Doce - 13. Trece. - --- gofmt -- -Text. - 1. Uno - 2. Dos - 3. Tres - 5. Cinco - 7. Siete - 11. Once - 12. Doce - 13. Trece. - --- text -- -Text. - 1. Uno - 2. Dos - 3. Tres - 5. Cinco - 7. Siete - 11. Once - 12. Doce - 13. Trece. - --- markdown -- -Text. - - 1. Uno - 2. Dos - 3. Tres - 5. Cinco - 7. Siete - 11. Once - 12. Doce - 13. Trece. - --- html -- -

Text. -

    -
  1. Uno -
  2. Dos -
  3. Tres -
  4. Cinco -
  5. Siete -
  6. Once -
  7. Doce -
  8. Trece. -
diff --git a/internal/backport/go/doc/comment/testdata/list3.txt b/internal/backport/go/doc/comment/testdata/list3.txt deleted file mode 100644 index d7d345d2d3..0000000000 --- a/internal/backport/go/doc/comment/testdata/list3.txt +++ /dev/null @@ -1,32 +0,0 @@ --- input -- -Text. - - 1. Uno - 1. Dos - 1. Tres - 1. Quatro - --- gofmt -- -Text. - - 1. Uno - 1. Dos - 1. Tres - 1. Quatro - --- markdown -- -Text. - - 1. Uno - 1. Dos - 1. Tres - 1. Quatro - --- html -- -

Text. -

    -
  1. Uno -
  2. Dos -
  3. Tres -
  4. Quatro -
diff --git a/internal/backport/go/doc/comment/testdata/list4.txt b/internal/backport/go/doc/comment/testdata/list4.txt deleted file mode 100644 index 9c28d65b6c..0000000000 --- a/internal/backport/go/doc/comment/testdata/list4.txt +++ /dev/null @@ -1,38 +0,0 @@ --- input -- -Text. - 1. List -2. Not indented, not a list. - 3. Another list. - --- gofmt -- -Text. - 1. List - -2. Not indented, not a list. - 3. Another list. - --- text -- -Text. - 1. List - -2. Not indented, not a list. - 3. Another list. - --- markdown -- -Text. - - 1. List - -2\. Not indented, not a list. - - 3. Another list. - --- html -- -

Text. -

    -
  1. List -
-

2. Not indented, not a list. -

    -
  1. Another list. -
diff --git a/internal/backport/go/doc/comment/testdata/list5.txt b/internal/backport/go/doc/comment/testdata/list5.txt deleted file mode 100644 index a5128e5b7c..0000000000 --- a/internal/backport/go/doc/comment/testdata/list5.txt +++ /dev/null @@ -1,40 +0,0 @@ --- input -- -Text. - - 1. One - 999999999999999999999. Big - 1000000000000000000000. Bigger - 1000000000000000000001. Biggest - --- gofmt -- -Text. - - 1. One - 999999999999999999999. Big - 1000000000000000000000. Bigger - 1000000000000000000001. Biggest - --- text -- -Text. - - 1. One - 999999999999999999999. Big - 1000000000000000000000. Bigger - 1000000000000000000001. Biggest - --- markdown -- -Text. - - 1. One - 999999999999999999999. Big - 1000000000000000000000. Bigger - 1000000000000000000001. Biggest - --- html -- -

Text. -

    -
  1. One -
  2. Big -
  3. Bigger -
  4. Biggest -
diff --git a/internal/backport/go/doc/comment/testdata/list6.txt b/internal/backport/go/doc/comment/testdata/list6.txt deleted file mode 100644 index ffc0122f52..0000000000 --- a/internal/backport/go/doc/comment/testdata/list6.txt +++ /dev/null @@ -1,129 +0,0 @@ --- input -- -Text. - - List immediately after. - - Another. - -More text. - - - List after blank line. - - Another. - -Even more text. - - List immediately after. - - - Blank line between items. - -Yet more text. - - - Another list after blank line. - - - Blank line between items. - -Still more text. - - One list item. - - Multiple paragraphs. --- dump -- -Doc - Paragraph - Plain "Text." - List ForceBlankBefore=false ForceBlankBetween=false - Item Number="" - Paragraph - Plain "List immediately after." - Item Number="" - Paragraph - Plain "Another." - Paragraph - Plain "More text." - List ForceBlankBefore=true ForceBlankBetween=false - Item Number="" - Paragraph - Plain "List after blank line." - Item Number="" - Paragraph - Plain "Another." - Paragraph - Plain "Even more text." - List ForceBlankBefore=false ForceBlankBetween=true - Item Number="" - Paragraph - Plain "List immediately after." - Item Number="" - Paragraph - Plain "Blank line between items." - Paragraph - Plain "Yet more text." - List ForceBlankBefore=true ForceBlankBetween=true - Item Number="" - Paragraph - Plain "Another list after blank line." - Item Number="" - Paragraph - Plain "Blank line between items." - Paragraph - Plain "Still more text." - List ForceBlankBefore=false ForceBlankBetween=true - Item Number="" - Paragraph - Plain "One list item." - Paragraph - Plain "Multiple paragraphs." - --- gofmt -- -Text. - - List immediately after. - - Another. - -More text. - - - List after blank line. - - Another. - -Even more text. - - - List immediately after. - - - Blank line between items. - -Yet more text. - - - Another list after blank line. - - - Blank line between items. - -Still more text. - - - One list item. - - Multiple paragraphs. - --- markdown -- -Text. - - - List immediately after. - - Another. - -More text. - - - List after blank line. - - Another. - -Even more text. - - - List immediately after. - - - Blank line between items. - -Yet more text. - - - Another list after blank line. - - - Blank line between items. - -Still more text. - - - One list item. - - Multiple paragraphs. - diff --git a/internal/backport/go/doc/comment/testdata/list7.txt b/internal/backport/go/doc/comment/testdata/list7.txt deleted file mode 100644 index 446605061f..0000000000 --- a/internal/backport/go/doc/comment/testdata/list7.txt +++ /dev/null @@ -1,98 +0,0 @@ --- input -- -Almost list markers (but not quite): - - - - -❦ - - - $ - -❦ - - - $ - -❦ - - $ - $ - -❦ - - 1! List. - -❦ --- gofmt -- -Almost list markers (but not quite): - - - - -❦ - - - $ - -❦ - - - $ - -❦ - -❦ - - 1! List. - -❦ --- text -- -Almost list markers (but not quite): - - - - -❦ - - - - -❦ - - - - -❦ - -❦ - - 1! List. - -❦ --- markdown -- -Almost list markers (but not quite): - - - - -❦ - - - $ - -❦ - - - $ - -❦ - -❦ - - 1! List. - -❦ --- html -- -

Almost list markers (but not quite): -

-
-
-

❦ -

- $
-
-

❦ -

- $
-
-

❦ -

❦ -

1! List.
-
-

❦ diff --git a/internal/backport/go/doc/comment/testdata/list8.txt b/internal/backport/go/doc/comment/testdata/list8.txt deleted file mode 100644 index fc46b0d835..0000000000 --- a/internal/backport/go/doc/comment/testdata/list8.txt +++ /dev/null @@ -1,56 +0,0 @@ --- input -- -Loose lists. - - A - - B - - C - D - - E - - F --- gofmt -- -Loose lists. - - - A - - B - - - C - D - - - E - - - F --- text -- -Loose lists. - - - A - - B - - - C D - - - E - - - F --- markdown -- -Loose lists. - - - A - - B - - - C D - - - E - - - F --- html -- -

Loose lists. -

    -
  • A -

    B -

  • C -D -

  • E -

  • F -

diff --git a/internal/backport/go/doc/comment/testdata/list9.txt b/internal/backport/go/doc/comment/testdata/list9.txt deleted file mode 100644 index 48e4673d54..0000000000 --- a/internal/backport/go/doc/comment/testdata/list9.txt +++ /dev/null @@ -1,30 +0,0 @@ --- input -- -Text. - -1. Not a list -2. because it is -3. unindented. - -4. This one - is a list - because of the indented text. -5. More wrapped - items. -6. And unwrapped. - -7. The blank line stops the heuristic. --- gofmt -- -Text. - -1. Not a list -2. because it is -3. unindented. - - 4. This one - is a list - because of the indented text. - 5. More wrapped - items. - 6. And unwrapped. - -7. The blank line stops the heuristic. diff --git a/internal/backport/go/doc/comment/testdata/para.txt b/internal/backport/go/doc/comment/testdata/para.txt deleted file mode 100644 index 2355fa8172..0000000000 --- a/internal/backport/go/doc/comment/testdata/para.txt +++ /dev/null @@ -1,17 +0,0 @@ --- input -- -Hello, world. -This is a paragraph. - --- gofmt -- -Hello, world. -This is a paragraph. - --- text -- -Hello, world. This is a paragraph. - --- markdown -- -Hello, world. This is a paragraph. - --- html -- -

Hello, world. -This is a paragraph. diff --git a/internal/backport/go/doc/comment/testdata/quote.txt b/internal/backport/go/doc/comment/testdata/quote.txt deleted file mode 100644 index b64adae0b3..0000000000 --- a/internal/backport/go/doc/comment/testdata/quote.txt +++ /dev/null @@ -1,15 +0,0 @@ --- input -- -Doubled single quotes like `` and '' turn into Unicode double quotes, -but single quotes ` and ' do not. -Misplaced markdown fences ``` do not either. --- gofmt -- -Doubled single quotes like “ and ” turn into Unicode double quotes, -but single quotes ` and ' do not. -Misplaced markdown fences ``` do not either. --- text -- -Doubled single quotes like “ and ” turn into Unicode double quotes, but single -quotes ` and ' do not. Misplaced markdown fences ``` do not either. --- html -- -

Doubled single quotes like “ and ” turn into Unicode double quotes, -but single quotes ` and ' do not. -Misplaced markdown fences ``` do not either. diff --git a/internal/backport/go/doc/comment/testdata/text.txt b/internal/backport/go/doc/comment/testdata/text.txt deleted file mode 100644 index c4de6e20d2..0000000000 --- a/internal/backport/go/doc/comment/testdata/text.txt +++ /dev/null @@ -1,62 +0,0 @@ -{"TextPrefix":"|", "TextCodePrefix": "@"} --- input -- -Hello, world - Code block here. -More text. -Tight list - - one - - two - - three -Loose list - - one - - - two - - - three - -# Heading - -More text. --- gofmt -- -Hello, world - - Code block here. - -More text. -Tight list - - one - - two - - three - -Loose list - - - one - - - two - - - three - -# Heading - -More text. --- text -- -|Hello, world -| -@Code block here. -| -|More text. Tight list -| - one -| - two -| - three -| -|Loose list -| -| - one -| -| - two -| -| - three -| -|# Heading -| -|More text. diff --git a/internal/backport/go/doc/comment/testdata/text2.txt b/internal/backport/go/doc/comment/testdata/text2.txt deleted file mode 100644 index a099d0b8c6..0000000000 --- a/internal/backport/go/doc/comment/testdata/text2.txt +++ /dev/null @@ -1,14 +0,0 @@ -{"TextWidth": -1} --- input -- -Package gob manages streams of gobs - binary values exchanged between an -Encoder (transmitter) and a Decoder (receiver). A typical use is -transporting arguments and results of remote procedure calls (RPCs) such as -those provided by package "net/rpc". - -The implementation compiles a custom codec for each data type in the stream -and is most efficient when a single Encoder is used to transmit a stream of -values, amortizing the cost of compilation. --- text -- -Package gob manages streams of gobs - binary values exchanged between an Encoder (transmitter) and a Decoder (receiver). A typical use is transporting arguments and results of remote procedure calls (RPCs) such as those provided by package "net/rpc". - -The implementation compiles a custom codec for each data type in the stream and is most efficient when a single Encoder is used to transmit a stream of values, amortizing the cost of compilation. diff --git a/internal/backport/go/doc/comment/testdata/text3.txt b/internal/backport/go/doc/comment/testdata/text3.txt deleted file mode 100644 index 75d2c3765c..0000000000 --- a/internal/backport/go/doc/comment/testdata/text3.txt +++ /dev/null @@ -1,28 +0,0 @@ -{"TextWidth": 30} --- input -- -Package gob manages streams of gobs - binary values exchanged between an -Encoder (transmitter) and a Decoder (receiver). A typical use is -transporting arguments and results of remote procedure calls (RPCs) such as -those provided by package "net/rpc". - -The implementation compiles a custom codec for each data type in the stream -and is most efficient when a single Encoder is used to transmit a stream of -values, amortizing the cost of compilation. --- text -- -Package gob manages streams -of gobs - binary values -exchanged between an Encoder -(transmitter) and a Decoder -(receiver). A typical use is -transporting arguments and -results of remote procedure -calls (RPCs) such as those -provided by package "net/rpc". - -The implementation compiles -a custom codec for each data -type in the stream and is -most efficient when a single -Encoder is used to transmit a -stream of values, amortizing -the cost of compilation. diff --git a/internal/backport/go/doc/comment/testdata/text4.txt b/internal/backport/go/doc/comment/testdata/text4.txt deleted file mode 100644 index e429985077..0000000000 --- a/internal/backport/go/doc/comment/testdata/text4.txt +++ /dev/null @@ -1,29 +0,0 @@ -{"TextWidth": 29} --- input -- -Package gob manages streams of gobs - binary values exchanged between an -Encoder (transmitter) and a Decoder (receiver). A typical use is -transporting arguments and results of remote procedure calls (RPCs) such as -those provided by package "net/rpc". - -The implementation compiles a custom codec for each data type in the stream -and is most efficient when a single Encoder is used to transmit a stream of -values, amortizing the cost of compilation. --- text -- -Package gob manages streams -of gobs - binary values -exchanged between an Encoder -(transmitter) and a Decoder -(receiver). A typical use -is transporting arguments -and results of remote -procedure calls (RPCs) such -as those provided by package -"net/rpc". - -The implementation compiles -a custom codec for each data -type in the stream and is -most efficient when a single -Encoder is used to transmit a -stream of values, amortizing -the cost of compilation. diff --git a/internal/backport/go/doc/comment/testdata/text5.txt b/internal/backport/go/doc/comment/testdata/text5.txt deleted file mode 100644 index 2408fc559d..0000000000 --- a/internal/backport/go/doc/comment/testdata/text5.txt +++ /dev/null @@ -1,38 +0,0 @@ -{"TextWidth": 20} --- input -- -Package gob manages streams of gobs - binary values exchanged between an -Encoder (transmitter) and a Decoder (receiver). A typical use is -transporting arguments and results of remote procedure calls (RPCs) such as -those provided by package "net/rpc". - -The implementation compiles a custom codec for each data type in the stream -and is most efficient when a single Encoder is used to transmit a stream of -values, amortizing the cost of compilation. --- text -- -Package gob -manages streams -of gobs - binary -values exchanged -between an Encoder -(transmitter) and a -Decoder (receiver). -A typical use -is transporting -arguments and -results of remote -procedure calls -(RPCs) such as those -provided by package -"net/rpc". - -The implementation -compiles a custom -codec for each -data type in the -stream and is most -efficient when a -single Encoder is -used to transmit a -stream of values, -amortizing the cost -of compilation. diff --git a/internal/backport/go/doc/comment/testdata/text6.txt b/internal/backport/go/doc/comment/testdata/text6.txt deleted file mode 100644 index d6deff52cf..0000000000 --- a/internal/backport/go/doc/comment/testdata/text6.txt +++ /dev/null @@ -1,18 +0,0 @@ --- input -- -Package gob manages streams of gobs - binary values exchanged between an -Encoder (transmitter) and a Decoder (receiver). A typical use is -transporting arguments and results of remote procedure calls (RPCs) such as -those provided by package "net/rpc". - -The implementation compiles a custom codec for each data type in the stream -and is most efficient when a single Encoder is used to transmit a stream of -values, amortizing the cost of compilation. --- text -- -Package gob manages streams of gobs - binary values exchanged between an Encoder -(transmitter) and a Decoder (receiver). A typical use is transporting arguments -and results of remote procedure calls (RPCs) such as those provided by package -"net/rpc". - -The implementation compiles a custom codec for each data type in the stream and -is most efficient when a single Encoder is used to transmit a stream of values, -amortizing the cost of compilation. diff --git a/internal/backport/go/doc/comment/testdata/text7.txt b/internal/backport/go/doc/comment/testdata/text7.txt deleted file mode 100644 index c9fb6d3754..0000000000 --- a/internal/backport/go/doc/comment/testdata/text7.txt +++ /dev/null @@ -1,21 +0,0 @@ -{"TextPrefix": " "} --- input -- -Package gob manages streams of gobs - binary values exchanged between an -Encoder (transmitter) and a Decoder (receiver). A typical use is -transporting arguments and results of remote procedure calls (RPCs) such as -those provided by package "net/rpc". - -The implementation compiles a custom codec for each data type in the stream -and is most efficient when a single Encoder is used to transmit a stream of -values, amortizing the cost of compilation. --- text -- - Package gob manages streams of gobs - binary values - exchanged between an Encoder (transmitter) and a Decoder - (receiver). A typical use is transporting arguments and - results of remote procedure calls (RPCs) such as those - provided by package "net/rpc". - - The implementation compiles a custom codec for each data - type in the stream and is most efficient when a single - Encoder is used to transmit a stream of values, amortizing - the cost of compilation. diff --git a/internal/backport/go/doc/comment/testdata/text8.txt b/internal/backport/go/doc/comment/testdata/text8.txt deleted file mode 100644 index 560ac951c1..0000000000 --- a/internal/backport/go/doc/comment/testdata/text8.txt +++ /dev/null @@ -1,94 +0,0 @@ -{"TextWidth": 40} --- input -- -If the arguments have version suffixes (like @latest or @v1.0.0), "go install" -builds packages in module-aware mode, ignoring the go.mod file in the current -directory or any parent directory, if there is one. This is useful for -installing executables without affecting the dependencies of the main module. -To eliminate ambiguity about which module versions are used in the build, the -arguments must satisfy the following constraints: - - - Arguments must be package paths or package patterns (with "..." wildcards). - They must not be standard packages (like fmt), meta-patterns (std, cmd, - all), or relative or absolute file paths. - - - All arguments must have the same version suffix. Different queries are not - allowed, even if they refer to the same version. - - - All arguments must refer to packages in the same module at the same version. - - - Package path arguments must refer to main packages. Pattern arguments - will only match main packages. - - - No module is considered the "main" module. If the module containing - packages named on the command line has a go.mod file, it must not contain - directives (replace and exclude) that would cause it to be interpreted - differently than if it were the main module. The module must not require - a higher version of itself. - - - Vendor directories are not used in any module. (Vendor directories are not - included in the module zip files downloaded by 'go install'.) - -If the arguments don't have version suffixes, "go install" may run in -module-aware mode or GOPATH mode, depending on the GO111MODULE environment -variable and the presence of a go.mod file. See 'go help modules' for details. -If module-aware mode is enabled, "go install" runs in the context of the main -module. --- text -- -If the arguments have version suffixes -(like @latest or @v1.0.0), "go install" -builds packages in module-aware mode, -ignoring the go.mod file in the current -directory or any parent directory, -if there is one. This is useful for -installing executables without affecting -the dependencies of the main module. -To eliminate ambiguity about which -module versions are used in the build, -the arguments must satisfy the following -constraints: - - - Arguments must be package paths - or package patterns (with "..." - wildcards). They must not be - standard packages (like fmt), - meta-patterns (std, cmd, all), - or relative or absolute file paths. - - - All arguments must have the same - version suffix. Different queries - are not allowed, even if they refer - to the same version. - - - All arguments must refer to packages - in the same module at the same - version. - - - Package path arguments must refer - to main packages. Pattern arguments - will only match main packages. - - - No module is considered the "main" - module. If the module containing - packages named on the command line - has a go.mod file, it must not - contain directives (replace and - exclude) that would cause it to be - interpreted differently than if it - were the main module. The module - must not require a higher version of - itself. - - - Vendor directories are not used in - any module. (Vendor directories are - not included in the module zip files - downloaded by 'go install'.) - -If the arguments don't have version -suffixes, "go install" may run in -module-aware mode or GOPATH mode, -depending on the GO111MODULE environment -variable and the presence of a go.mod -file. See 'go help modules' for details. -If module-aware mode is enabled, -"go install" runs in the context of the -main module. diff --git a/internal/backport/go/doc/comment/testdata/text9.txt b/internal/backport/go/doc/comment/testdata/text9.txt deleted file mode 100644 index 07a64aa227..0000000000 --- a/internal/backport/go/doc/comment/testdata/text9.txt +++ /dev/null @@ -1,12 +0,0 @@ -{"TextPrefix":"|", "TextCodePrefix": "@"} --- input -- -Hello, world - Code block here. --- gofmt -- -Hello, world - - Code block here. --- text -- -|Hello, world -| -@Code block here. diff --git a/internal/backport/go/doc/comment/testdata/words.txt b/internal/backport/go/doc/comment/testdata/words.txt deleted file mode 100644 index 63c7e1a1b2..0000000000 --- a/internal/backport/go/doc/comment/testdata/words.txt +++ /dev/null @@ -1,10 +0,0 @@ --- input -- -This is an italicword and a linkedword and Unicöde. --- gofmt -- -This is an italicword and a linkedword and Unicöde. --- text -- -This is an italicword and a linkedword and Unicöde. --- markdown -- -This is an *italicword* and a [*linkedword*](https://example.com/linkedword) and Unicöde. --- html -- -

This is an italicword and a linkedword and Unicöde. diff --git a/internal/backport/go/doc/comment/testdata_test.go b/internal/backport/go/doc/comment/testdata_test.go deleted file mode 100644 index fa952d21d0..0000000000 --- a/internal/backport/go/doc/comment/testdata_test.go +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2022 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 comment - -import ( - "bytes" - "encoding/json" - "fmt" - "path/filepath" - "strings" - "testing" - - "golang.org/x/tools/txtar" - "golang.org/x/website/internal/backport/diff" -) - -func TestTestdata(t *testing.T) { - files, _ := filepath.Glob("testdata/*.txt") - if len(files) == 0 { - t.Fatalf("no testdata") - } - var p Parser - p.Words = map[string]string{ - "italicword": "", - "linkedword": "https://example.com/linkedword", - } - p.LookupPackage = func(name string) (importPath string, ok bool) { - if name == "comment" { - return "go/doc/comment", true - } - return DefaultLookupPackage(name) - } - p.LookupSym = func(recv, name string) (ok bool) { - if recv == "Parser" && name == "Parse" || - recv == "" && name == "Doc" || - recv == "" && name == "NoURL" { - return true - } - return false - } - - stripDollars := func(b []byte) []byte { - // Remove trailing $ on lines. - // They make it easier to see lines with trailing spaces, - // as well as turning them into lines without trailing spaces, - // in case editors remove trailing spaces. - return bytes.ReplaceAll(b, []byte("$\n"), []byte("\n")) - } - for _, file := range files { - t.Run(filepath.Base(file), func(t *testing.T) { - var pr Printer - a, err := txtar.ParseFile(file) - if err != nil { - t.Fatal(err) - } - if len(a.Comment) > 0 { - err := json.Unmarshal(a.Comment, &pr) - if err != nil { - t.Fatalf("unmarshalling top json: %v", err) - } - } - if len(a.Files) < 1 || a.Files[0].Name != "input" { - t.Fatalf("first file is not %q", "input") - } - d := p.Parse(string(stripDollars(a.Files[0].Data))) - for _, f := range a.Files[1:] { - want := stripDollars(f.Data) - for len(want) >= 2 && want[len(want)-1] == '\n' && want[len(want)-2] == '\n' { - want = want[:len(want)-1] - } - var out []byte - switch f.Name { - default: - t.Fatalf("unknown output file %q", f.Name) - case "dump": - out = dump(d) - case "gofmt": - out = pr.Comment(d) - case "html": - out = pr.HTML(d) - case "markdown": - out = pr.Markdown(d) - case "text": - out = pr.Text(d) - } - if string(out) != string(want) { - t.Errorf("%s: %s", file, diff.Diff(f.Name, want, "have", out)) - } - } - }) - } -} - -func dump(d *Doc) []byte { - var out bytes.Buffer - dumpTo(&out, 0, d) - return out.Bytes() -} - -func dumpTo(out *bytes.Buffer, indent int, x interface{}) { - switch x := x.(type) { - default: - fmt.Fprintf(out, "?%T", x) - - case *Doc: - fmt.Fprintf(out, "Doc") - dumpTo(out, indent+1, x.Content) - if len(x.Links) > 0 { - dumpNL(out, indent+1) - fmt.Fprintf(out, "Links") - dumpTo(out, indent+2, x.Links) - } - fmt.Fprintf(out, "\n") - - case []*LinkDef: - for _, def := range x { - dumpNL(out, indent) - dumpTo(out, indent, def) - } - - case *LinkDef: - fmt.Fprintf(out, "LinkDef Used:%v Text:%q URL:%s", x.Used, x.Text, x.URL) - - case []Block: - for _, blk := range x { - dumpNL(out, indent) - dumpTo(out, indent, blk) - } - - case *Heading: - fmt.Fprintf(out, "Heading") - dumpTo(out, indent+1, x.Text) - - case *List: - fmt.Fprintf(out, "List ForceBlankBefore=%v ForceBlankBetween=%v", x.ForceBlankBefore, x.ForceBlankBetween) - dumpTo(out, indent+1, x.Items) - - case []*ListItem: - for _, item := range x { - dumpNL(out, indent) - dumpTo(out, indent, item) - } - - case *ListItem: - fmt.Fprintf(out, "Item Number=%q", x.Number) - dumpTo(out, indent+1, x.Content) - - case *Paragraph: - fmt.Fprintf(out, "Paragraph") - dumpTo(out, indent+1, x.Text) - - case *Code: - fmt.Fprintf(out, "Code") - dumpTo(out, indent+1, x.Text) - - case []Text: - for _, t := range x { - dumpNL(out, indent) - dumpTo(out, indent, t) - } - - case Plain: - if !strings.Contains(string(x), "\n") { - fmt.Fprintf(out, "Plain %q", string(x)) - } else { - fmt.Fprintf(out, "Plain") - dumpTo(out, indent+1, string(x)) - } - - case Italic: - if !strings.Contains(string(x), "\n") { - fmt.Fprintf(out, "Italic %q", string(x)) - } else { - fmt.Fprintf(out, "Italic") - dumpTo(out, indent+1, string(x)) - } - - case string: - for _, line := range strings.SplitAfter(x, "\n") { - if line != "" { - dumpNL(out, indent) - fmt.Fprintf(out, "%q", line) - } - } - - case *Link: - fmt.Fprintf(out, "Link %q", x.URL) - dumpTo(out, indent+1, x.Text) - - case *DocLink: - fmt.Fprintf(out, "DocLink pkg:%q, recv:%q, name:%q", x.ImportPath, x.Recv, x.Name) - dumpTo(out, indent+1, x.Text) - } -} - -func dumpNL(out *bytes.Buffer, n int) { - out.WriteByte('\n') - for i := 0; i < n; i++ { - out.WriteByte('\t') - } -} diff --git a/internal/backport/go/doc/comment/text.go b/internal/backport/go/doc/comment/text.go deleted file mode 100644 index 4ab2da4712..0000000000 --- a/internal/backport/go/doc/comment/text.go +++ /dev/null @@ -1,337 +0,0 @@ -// Copyright 2022 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 comment - -import ( - "bytes" - "fmt" - "sort" - "strings" - "unicode/utf8" -) - -// A textPrinter holds the state needed for printing a Doc as plain text. -type textPrinter struct { - *Printer - long strings.Builder - prefix string - codePrefix string - width int -} - -// Text returns a textual formatting of the Doc. -// See the [Printer] documentation for ways to customize the text output. -func (p *Printer) Text(d *Doc) []byte { - tp := &textPrinter{ - Printer: p, - prefix: p.TextPrefix, - codePrefix: p.TextCodePrefix, - width: p.TextWidth, - } - if tp.codePrefix == "" { - tp.codePrefix = p.TextPrefix + "\t" - } - if tp.width == 0 { - tp.width = 80 - utf8.RuneCountInString(tp.prefix) - } - - var out bytes.Buffer - for i, x := range d.Content { - if i > 0 && blankBefore(x) { - out.WriteString(tp.prefix) - writeNL(&out) - } - tp.block(&out, x) - } - anyUsed := false - for _, def := range d.Links { - if def.Used { - anyUsed = true - break - } - } - if anyUsed { - writeNL(&out) - for _, def := range d.Links { - if def.Used { - fmt.Fprintf(&out, "[%s]: %s\n", def.Text, def.URL) - } - } - } - return out.Bytes() -} - -// writeNL calls out.WriteByte('\n') -// but first trims trailing spaces on the previous line. -func writeNL(out *bytes.Buffer) { - // Trim trailing spaces. - data := out.Bytes() - n := 0 - for n < len(data) && (data[len(data)-n-1] == ' ' || data[len(data)-n-1] == '\t') { - n++ - } - if n > 0 { - out.Truncate(len(data) - n) - } - out.WriteByte('\n') -} - -// block prints the block x to out. -func (p *textPrinter) block(out *bytes.Buffer, x Block) { - switch x := x.(type) { - default: - fmt.Fprintf(out, "?%T\n", x) - - case *Paragraph: - out.WriteString(p.prefix) - p.text(out, "", x.Text) - - case *Heading: - out.WriteString(p.prefix) - out.WriteString("# ") - p.text(out, "", x.Text) - - case *Code: - text := x.Text - for text != "" { - var line string - line, text, _ = stringsCut(text, "\n") - if line != "" { - out.WriteString(p.codePrefix) - out.WriteString(line) - } - writeNL(out) - } - - case *List: - loose := x.BlankBetween() - for i, item := range x.Items { - if i > 0 && loose { - out.WriteString(p.prefix) - writeNL(out) - } - out.WriteString(p.prefix) - out.WriteString(" ") - if item.Number == "" { - out.WriteString(" - ") - } else { - out.WriteString(item.Number) - out.WriteString(". ") - } - for i, blk := range item.Content { - const fourSpace = " " - if i > 0 { - writeNL(out) - out.WriteString(p.prefix) - out.WriteString(fourSpace) - } - p.text(out, fourSpace, blk.(*Paragraph).Text) - } - } - } -} - -// text prints the text sequence x to out. -func (p *textPrinter) text(out *bytes.Buffer, indent string, x []Text) { - p.oneLongLine(&p.long, x) - words := strings.Fields(p.long.String()) - p.long.Reset() - - var seq []int - if p.width < 0 || len(words) == 0 { - seq = []int{0, len(words)} // one long line - } else { - seq = wrap(words, p.width-utf8.RuneCountInString(indent)) - } - for i := 0; i+1 < len(seq); i++ { - if i > 0 { - out.WriteString(p.prefix) - out.WriteString(indent) - } - for j, w := range words[seq[i]:seq[i+1]] { - if j > 0 { - out.WriteString(" ") - } - out.WriteString(w) - } - writeNL(out) - } -} - -// oneLongLine prints the text sequence x to out as one long line, -// without worrying about line wrapping. -// Explicit links have the [ ] dropped to improve readability. -func (p *textPrinter) oneLongLine(out *strings.Builder, x []Text) { - for _, t := range x { - switch t := t.(type) { - case Plain: - out.WriteString(string(t)) - case Italic: - out.WriteString(string(t)) - case *Link: - p.oneLongLine(out, t.Text) - case *DocLink: - p.oneLongLine(out, t.Text) - } - } -} - -// wrap wraps words into lines of at most max runes, -// minimizing the sum of the squares of the leftover lengths -// at the end of each line (except the last, of course), -// with a preference for ending lines at punctuation (.,:;). -// -// The returned slice gives the indexes of the first words -// on each line in the wrapped text with a final entry of len(words). -// Thus the lines are words[seq[0]:seq[1]], words[seq[1]:seq[2]], -// ..., words[seq[len(seq)-2]:seq[len(seq)-1]]. -// -// The implementation runs in O(n log n) time, where n = len(words), -// using the algorithm described in D. S. Hirschberg and L. L. Larmore, -// “[The least weight subsequence problem],” FOCS 1985, pp. 137-143. -// -// [The least weight subsequence problem]: https://doi.org/10.1109/SFCS.1985.60 -func wrap(words []string, max int) (seq []int) { - // The algorithm requires that our scoring function be concave, - // meaning that for all i₀ ≤ i₁ < j₀ ≤ j₁, - // weight(i₀, j₀) + weight(i₁, j₁) ≤ weight(i₀, j₁) + weight(i₁, j₀). - // - // Our weights are two-element pairs [hi, lo] - // ordered by elementwise comparison. - // The hi entry counts the weight for lines that are longer than max, - // and the lo entry counts the weight for lines that are not. - // This forces the algorithm to first minimize the number of lines - // that are longer than max, which correspond to lines with - // single very long words. Having done that, it can move on to - // minimizing the lo score, which is more interesting. - // - // The lo score is the sum for each line of the square of the - // number of spaces remaining at the end of the line and a - // penalty of 64 given out for not ending the line in a - // punctuation character (.,:;). - // The penalty is somewhat arbitrarily chosen by trying - // different amounts and judging how nice the wrapped text looks. - // Roughly speaking, using 64 means that we are willing to - // end a line with eight blank spaces in order to end at a - // punctuation character, even if the next word would fit in - // those spaces. - // - // We care about ending in punctuation characters because - // it makes the text easier to skim if not too many sentences - // or phrases begin with a single word on the previous line. - - // A score is the score (also called weight) for a given line. - // add and cmp add and compare scores. - type score struct { - hi int64 - lo int64 - } - add := func(s, t score) score { return score{s.hi + t.hi, s.lo + t.lo} } - cmp := func(s, t score) int { - switch { - case s.hi < t.hi: - return -1 - case s.hi > t.hi: - return +1 - case s.lo < t.lo: - return -1 - case s.lo > t.lo: - return +1 - } - return 0 - } - - // total[j] is the total number of runes - // (including separating spaces) in words[:j]. - total := make([]int, len(words)+1) - total[0] = 0 - for i, s := range words { - total[1+i] = total[i] + utf8.RuneCountInString(s) + 1 - } - - // weight returns weight(i, j). - weight := func(i, j int) score { - // On the last line, there is zero weight for being too short. - n := total[j] - 1 - total[i] - if j == len(words) && n <= max { - return score{0, 0} - } - - // Otherwise the weight is the penalty plus the square of the number of - // characters remaining on the line or by which the line goes over. - // In the latter case, that value goes in the hi part of the score. - // (See note above.) - p := wrapPenalty(words[j-1]) - v := int64(max-n) * int64(max-n) - if n > max { - return score{v, p} - } - return score{0, v + p} - } - - // The rest of this function is “The Basic Algorithm” from - // Hirschberg and Larmore's conference paper, - // using the same names as in the paper. - f := []score{{0, 0}} - g := func(i, j int) score { return add(f[i], weight(i, j)) } - - bridge := func(a, b, c int) bool { - k := c + sort.Search(len(words)+1-c, func(k int) bool { - k += c - return cmp(g(a, k), g(b, k)) > 0 - }) - if k > len(words) { - return true - } - return cmp(g(c, k), g(b, k)) <= 0 - } - - // d is a one-ended deque implemented as a slice. - d := make([]int, 1, len(words)) - d[0] = 0 - bestleft := make([]int, 1, len(words)) - bestleft[0] = -1 - for m := 1; m < len(words); m++ { - f = append(f, g(d[0], m)) - bestleft = append(bestleft, d[0]) - for len(d) > 1 && cmp(g(d[1], m+1), g(d[0], m+1)) <= 0 { - d = d[1:] // “Retire” - } - for len(d) > 1 && bridge(d[len(d)-2], d[len(d)-1], m) { - d = d[:len(d)-1] // “Fire” - } - if cmp(g(m, len(words)), g(d[len(d)-1], len(words))) < 0 { - d = append(d, m) // “Hire” - // The next few lines are not in the paper but are necessary - // to handle two-word inputs correctly. It appears to be - // just a bug in the paper's pseudocode. - if len(d) == 2 && cmp(g(d[1], m+1), g(d[0], m+1)) <= 0 { - d = d[1:] - } - } - } - bestleft = append(bestleft, d[0]) - - // Recover least weight sequence from bestleft. - n := 1 - for m := len(words); m > 0; m = bestleft[m] { - n++ - } - seq = make([]int, n) - for m := len(words); m > 0; m = bestleft[m] { - n-- - seq[n] = m - } - return seq -} - -// wrapPenalty is the penalty for inserting a line break after word s. -func wrapPenalty(s string) int64 { - switch s[len(s)-1] { - case '.', ',', ':', ';': - return 0 - } - return 64 -} diff --git a/internal/backport/go/doc/comment/wrap_test.go b/internal/backport/go/doc/comment/wrap_test.go deleted file mode 100644 index f9802c9c44..0000000000 --- a/internal/backport/go/doc/comment/wrap_test.go +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2022 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 comment - -import ( - "flag" - "fmt" - "math/rand" - "testing" - "time" - "unicode/utf8" -) - -var wrapSeed = flag.Int64("wrapseed", 0, "use `seed` for wrap test (default auto-seeds)") - -func TestWrap(t *testing.T) { - if *wrapSeed == 0 { - *wrapSeed = time.Now().UnixNano() - } - t.Logf("-wrapseed=%#x\n", *wrapSeed) - r := rand.New(rand.NewSource(*wrapSeed)) - - // Generate words of random length. - s := "1234567890αβcdefghijklmnopqrstuvwxyz" - sN := utf8.RuneCountInString(s) - var words []string - for i := 0; i < 100; i++ { - n := 1 + r.Intn(sN-1) - if n >= 12 { - n++ // extra byte for β - } - if n >= 11 { - n++ // extra byte for α - } - words = append(words, s[:n]) - } - - for n := 1; n <= len(words) && !t.Failed(); n++ { - t.Run(fmt.Sprint("n=", n), func(t *testing.T) { - words := words[:n] - t.Logf("words: %v", words) - for max := 1; max < 100 && !t.Failed(); max++ { - t.Run(fmt.Sprint("max=", max), func(t *testing.T) { - seq := wrap(words, max) - - // Compute score for seq. - start := 0 - score := int64(0) - if len(seq) == 0 { - t.Fatalf("wrap seq is empty") - } - if seq[0] != 0 { - t.Fatalf("wrap seq does not start with 0") - } - for _, n := range seq[1:] { - if n <= start { - t.Fatalf("wrap seq is non-increasing: %v", seq) - } - if n > len(words) { - t.Fatalf("wrap seq contains %d > %d: %v", n, len(words), seq) - } - size := -1 - for _, s := range words[start:n] { - size += 1 + utf8.RuneCountInString(s) - } - if n-start == 1 && size >= max { - // no score - } else if size > max { - t.Fatalf("wrap used overlong line %d:%d: %v", start, n, words[start:n]) - } else if n != len(words) { - score += int64(max-size)*int64(max-size) + wrapPenalty(words[n-1]) - } - start = n - } - if start != len(words) { - t.Fatalf("wrap seq does not use all words (%d < %d): %v", start, len(words), seq) - } - - // Check that score matches slow reference implementation. - slowSeq, slowScore := wrapSlow(words, max) - if score != slowScore { - t.Fatalf("wrap score = %d != wrapSlow score %d\nwrap: %v\nslow: %v", score, slowScore, seq, slowSeq) - } - }) - } - }) - } -} - -// wrapSlow is an O(n²) reference implementation for wrap. -// It returns a minimal-score sequence along with the score. -// It is OK if wrap returns a different sequence as long as that -// sequence has the same score. -func wrapSlow(words []string, max int) (seq []int, score int64) { - // Quadratic dynamic programming algorithm for line wrapping problem. - // best[i] tracks the best score possible for words[:i], - // assuming that for i < len(words) the line breaks after those words. - // bestleft[i] tracks the previous line break for best[i]. - best := make([]int64, len(words)+1) - bestleft := make([]int, len(words)+1) - best[0] = 0 - for i, w := range words { - if utf8.RuneCountInString(w) >= max { - // Overlong word must appear on line by itself. No effect on score. - best[i+1] = best[i] - continue - } - best[i+1] = 1e18 - p := wrapPenalty(w) - n := -1 - for j := i; j >= 0; j-- { - n += 1 + utf8.RuneCountInString(words[j]) - if n > max { - break - } - line := int64(n-max)*int64(n-max) + p - if i == len(words)-1 { - line = 0 // no score for final line being too short - } - s := best[j] + line - if best[i+1] > s { - best[i+1] = s - bestleft[i+1] = j - } - } - } - - // Recover least weight sequence from bestleft. - n := 1 - for m := len(words); m > 0; m = bestleft[m] { - n++ - } - seq = make([]int, n) - for m := len(words); m > 0; m = bestleft[m] { - n-- - seq[n] = m - } - return seq, best[len(words)] -} diff --git a/internal/backport/go/doc/comment_test.go b/internal/backport/go/doc/comment_test.go deleted file mode 100644 index fba8a7581a..0000000000 --- a/internal/backport/go/doc/comment_test.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2022 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 doc - -import ( - "bytes" - "testing" - - "golang.org/x/website/internal/backport/diff" - "golang.org/x/website/internal/backport/go/parser" - "golang.org/x/website/internal/backport/go/token" -) - -func TestComment(t *testing.T) { - fset := token.NewFileSet() - pkgs, err := parser.ParseDir(fset, "testdata/pkgdoc", nil, parser.ParseComments) - if err != nil { - t.Fatal(err) - } - if pkgs["pkgdoc"] == nil { - t.Fatal("missing package pkgdoc") - } - pkg := New(pkgs["pkgdoc"], "testdata/pkgdoc", 0) - - var ( - input = "[T] and [U] are types, and [T.M] is a method, but [V] is a broken link. [rand.Int] and [crand.Reader] are things.\n" - wantHTML = `

T and U are types, and T.M is a method, but [V] is a broken link. rand.Int and crand.Reader are things.` + "\n" - wantOldHTML = "

[T] and [U] are types, and [T.M] is a method, but [V] is a broken link. [rand.Int] and [crand.Reader] are things.\n" - wantMarkdown = "[T](#T) and [U](#U) are types, and [T.M](#T.M) is a method, but \\[V] is a broken link. [rand.Int](/math/rand#Int) and [crand.Reader](/crypto/rand#Reader) are things.\n" - wantText = "T and U are types, and T.M is a method, but [V] is a broken link. rand.Int and\ncrand.Reader are things.\n" - wantOldText = "[T] and [U] are types, and [T.M] is a method, but [V] is a broken link.\n[rand.Int] and [crand.Reader] are things.\n" - wantSynopsis = "T and U are types, and T.M is a method, but [V] is a broken link." - wantOldSynopsis = "[T] and [U] are types, and [T.M] is a method, but [V] is a broken link." - ) - - if b := pkg.HTML(input); string(b) != wantHTML { - t.Errorf("%s", diff.Diff("pkg.HTML", b, "want", []byte(wantHTML))) - } - if b := pkg.Markdown(input); string(b) != wantMarkdown { - t.Errorf("%s", diff.Diff("pkg.Markdown", b, "want", []byte(wantMarkdown))) - } - if b := pkg.Text(input); string(b) != wantText { - t.Errorf("%s", diff.Diff("pkg.Text", b, "want", []byte(wantText))) - } - if b := pkg.Synopsis(input); b != wantSynopsis { - t.Errorf("%s", diff.Diff("pkg.Synopsis", []byte(b), "want", []byte(wantText))) - } - - var buf bytes.Buffer - - buf.Reset() - ToHTML(&buf, input, map[string]string{"types": ""}) - if b := buf.Bytes(); string(b) != wantOldHTML { - t.Errorf("%s", diff.Diff("ToHTML", b, "want", []byte(wantOldHTML))) - } - - buf.Reset() - ToText(&buf, input, "", "\t", 80) - if b := buf.Bytes(); string(b) != wantOldText { - t.Errorf("%s", diff.Diff("ToText", b, "want", []byte(wantOldText))) - } - - if b := Synopsis(input); b != wantOldSynopsis { - t.Errorf("%s", diff.Diff("Synopsis", []byte(b), "want", []byte(wantOldText))) - } -} diff --git a/internal/backport/go/doc/doc.go b/internal/backport/go/doc/doc.go deleted file mode 100644 index ba1dd39104..0000000000 --- a/internal/backport/go/doc/doc.go +++ /dev/null @@ -1,350 +0,0 @@ -// Copyright 2009 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 doc extracts source code documentation from a Go AST. -package doc - -import ( - "fmt" - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/doc/comment" - "golang.org/x/website/internal/backport/go/token" - "strings" -) - -// Package is the documentation for an entire package. -type Package struct { - Doc string - Name string - ImportPath string - Imports []string - Filenames []string - Notes map[string][]*Note - - // Deprecated: For backward compatibility Bugs is still populated, - // but all new code should use Notes instead. - Bugs []string - - // declarations - Consts []*Value - Types []*Type - Vars []*Value - Funcs []*Func - - // Examples is a sorted list of examples associated with - // the package. Examples are extracted from _test.go files - // provided to NewFromFiles. - Examples []*Example - - importByName map[string]string - syms map[string]bool -} - -// Value is the documentation for a (possibly grouped) var or const declaration. -type Value struct { - Doc string - Names []string // var or const names in declaration order - Decl *ast.GenDecl - - order int -} - -// Type is the documentation for a type declaration. -type Type struct { - Doc string - Name string - Decl *ast.GenDecl - - // associated declarations - Consts []*Value // sorted list of constants of (mostly) this type - Vars []*Value // sorted list of variables of (mostly) this type - Funcs []*Func // sorted list of functions returning this type - Methods []*Func // sorted list of methods (including embedded ones) of this type - - // Examples is a sorted list of examples associated with - // this type. Examples are extracted from _test.go files - // provided to NewFromFiles. - Examples []*Example -} - -// Func is the documentation for a func declaration. -type Func struct { - Doc string - Name string - Decl *ast.FuncDecl - - // methods - // (for functions, these fields have the respective zero value) - Recv string // actual receiver "T" or "*T" - Orig string // original receiver "T" or "*T" - Level int // embedding level; 0 means not embedded - - // Examples is a sorted list of examples associated with this - // function or method. Examples are extracted from _test.go files - // provided to NewFromFiles. - Examples []*Example -} - -// A Note represents a marked comment starting with "MARKER(uid): note body". -// Any note with a marker of 2 or more upper case [A-Z] letters and a uid of -// at least one character is recognized. The ":" following the uid is optional. -// Notes are collected in the Package.Notes map indexed by the notes marker. -type Note struct { - Pos, End token.Pos // position range of the comment containing the marker - UID string // uid found with the marker - Body string // note body text -} - -// Mode values control the operation of New and NewFromFiles. -type Mode int - -const ( - // AllDecls says to extract documentation for all package-level - // declarations, not just exported ones. - AllDecls Mode = 1 << iota - - // AllMethods says to show all embedded methods, not just the ones of - // invisible (unexported) anonymous fields. - AllMethods - - // PreserveAST says to leave the AST unmodified. Originally, pieces of - // the AST such as function bodies were nil-ed out to save memory in - // godoc, but not all programs want that behavior. - PreserveAST -) - -// New computes the package documentation for the given package AST. -// New takes ownership of the AST pkg and may edit or overwrite it. -// To have the Examples fields populated, use NewFromFiles and include -// the package's _test.go files. -func New(pkg *ast.Package, importPath string, mode Mode) *Package { - var r reader - r.readPackage(pkg, mode) - r.computeMethodSets() - r.cleanupTypes() - p := &Package{ - Doc: r.doc, - Name: pkg.Name, - ImportPath: importPath, - Imports: sortedKeys(r.imports), - Filenames: r.filenames, - Notes: r.notes, - Bugs: noteBodies(r.notes["BUG"]), - Consts: sortedValues(r.values, token.CONST), - Types: sortedTypes(r.types, mode&AllMethods != 0), - Vars: sortedValues(r.values, token.VAR), - Funcs: sortedFuncs(r.funcs, true), - - importByName: r.importByName, - syms: make(map[string]bool), - } - - p.collectValues(p.Consts) - p.collectValues(p.Vars) - p.collectTypes(p.Types) - p.collectFuncs(p.Funcs) - - return p -} - -func (p *Package) collectValues(values []*Value) { - for _, v := range values { - for _, name := range v.Names { - p.syms[name] = true - } - } -} - -func (p *Package) collectTypes(types []*Type) { - for _, t := range types { - if p.syms[t.Name] { - // Shouldn't be any cycles but stop just in case. - continue - } - p.syms[t.Name] = true - p.collectValues(t.Consts) - p.collectValues(t.Vars) - p.collectFuncs(t.Funcs) - p.collectFuncs(t.Methods) - } -} - -func (p *Package) collectFuncs(funcs []*Func) { - for _, f := range funcs { - if f.Recv != "" { - p.syms[strings.TrimPrefix(f.Recv, "*")+"."+f.Name] = true - } else { - p.syms[f.Name] = true - } - } -} - -// NewFromFiles computes documentation for a package. -// -// The package is specified by a list of *ast.Files and corresponding -// file set, which must not be nil. -// NewFromFiles uses all provided files when computing documentation, -// so it is the caller's responsibility to provide only the files that -// match the desired build context. "go/build".Context.MatchFile can -// be used for determining whether a file matches a build context with -// the desired GOOS and GOARCH values, and other build constraints. -// The import path of the package is specified by importPath. -// -// Examples found in _test.go files are associated with the corresponding -// type, function, method, or the package, based on their name. -// If the example has a suffix in its name, it is set in the -// Example.Suffix field. Examples with malformed names are skipped. -// -// Optionally, a single extra argument of type Mode can be provided to -// control low-level aspects of the documentation extraction behavior. -// -// NewFromFiles takes ownership of the AST files and may edit them, -// unless the PreserveAST Mode bit is on. -func NewFromFiles(fset *token.FileSet, files []*ast.File, importPath string, opts ...interface{}) (*Package, error) { - // Check for invalid API usage. - if fset == nil { - panic(fmt.Errorf("doc.NewFromFiles: no token.FileSet provided (fset == nil)")) - } - var mode Mode - switch len(opts) { // There can only be 0 or 1 options, so a simple switch works for now. - case 0: - // Nothing to do. - case 1: - m, ok := opts[0].(Mode) - if !ok { - panic(fmt.Errorf("doc.NewFromFiles: option argument type must be doc.Mode")) - } - mode = m - default: - panic(fmt.Errorf("doc.NewFromFiles: there must not be more than 1 option argument")) - } - - // Collect .go and _test.go files. - var ( - goFiles = make(map[string]*ast.File) - testGoFiles []*ast.File - ) - for i := range files { - f := fset.File(files[i].Pos()) - if f == nil { - return nil, fmt.Errorf("file files[%d] is not found in the provided file set", i) - } - switch name := f.Name(); { - case strings.HasSuffix(name, ".go") && !strings.HasSuffix(name, "_test.go"): - goFiles[name] = files[i] - case strings.HasSuffix(name, "_test.go"): - testGoFiles = append(testGoFiles, files[i]) - default: - return nil, fmt.Errorf("file files[%d] filename %q does not have a .go extension", i, name) - } - } - - // TODO(dmitshur,gri): A relatively high level call to ast.NewPackage with a simpleImporter - // ast.Importer implementation is made below. It might be possible to short-circuit and simplify. - - // Compute package documentation. - pkg, _ := ast.NewPackage(fset, goFiles, simpleImporter, nil) // Ignore errors that can happen due to unresolved identifiers. - p := New(pkg, importPath, mode) - classifyExamples(p, Examples(testGoFiles...)) - return p, nil -} - -// simpleImporter returns a (dummy) package object named by the last path -// component of the provided package path (as is the convention for packages). -// This is sufficient to resolve package identifiers without doing an actual -// import. It never returns an error. -func simpleImporter(imports map[string]*ast.Object, path string) (*ast.Object, error) { - pkg := imports[path] - if pkg == nil { - // note that strings.LastIndex returns -1 if there is no "/" - pkg = ast.NewObj(ast.Pkg, path[strings.LastIndex(path, "/")+1:]) - pkg.Data = ast.NewScope(nil) // required by ast.NewPackage for dot-import - imports[path] = pkg - } - return pkg, nil -} - -// lookupSym reports whether the package has a given symbol or method. -// -// If recv == "", HasSym reports whether the package has a top-level -// const, func, type, or var named name. -// -// If recv != "", HasSym reports whether the package has a type -// named recv with a method named name. -func (p *Package) lookupSym(recv, name string) bool { - if recv != "" { - return p.syms[recv+"."+name] - } - return p.syms[name] -} - -// lookupPackage returns the import path identified by name -// in the given package. If name uniquely identifies a single import, -// then lookupPackage returns that import. -// If multiple packages are imported as name, importPath returns "", false. -// Otherwise, if name is the name of p itself, importPath returns "", true, -// to signal a reference to p. -// Otherwise, importPath returns "", false. -func (p *Package) lookupPackage(name string) (importPath string, ok bool) { - if path, ok := p.importByName[name]; ok { - if path == "" { - return "", false // multiple imports used the name - } - return path, true // found import - } - if p.Name == name { - return "", true // allow reference to this package - } - return "", false // unknown name -} - -// Parser returns a doc comment parser configured -// for parsing doc comments from package p. -// Each call returns a new parser, so that the caller may -// customize it before use. -func (p *Package) Parser() *comment.Parser { - return &comment.Parser{ - LookupPackage: p.lookupPackage, - LookupSym: p.lookupSym, - } -} - -// Printer returns a doc comment printer configured -// for printing doc comments from package p. -// Each call returns a new printer, so that the caller may -// customize it before use. -func (p *Package) Printer() *comment.Printer { - // No customization today, but having p.Printer() - // gives us flexibility in the future, and it is convenient for callers. - return &comment.Printer{} -} - -// HTML returns formatted HTML for the doc comment text. -// -// To customize details of the HTML, use [Package.Printer] -// to obtain a [comment.Printer], and configure it -// before calling its HTML method. -func (p *Package) HTML(text string) []byte { - return p.Printer().HTML(p.Parser().Parse(text)) -} - -// Markdown returns formatted Markdown for the doc comment text. -// -// To customize details of the Markdown, use [Package.Printer] -// to obtain a [comment.Printer], and configure it -// before calling its Markdown method. -func (p *Package) Markdown(text string) []byte { - return p.Printer().Markdown(p.Parser().Parse(text)) -} - -// Text returns formatted text for the doc comment text, -// wrapped to 80 Unicode code points and using tabs for -// code block indentation. -// -// To customize details of the formatting, use [Package.Printer] -// to obtain a [comment.Printer], and configure it -// before calling its Text method. -func (p *Package) Text(text string) []byte { - return p.Printer().Text(p.Parser().Parse(text)) -} diff --git a/internal/backport/go/doc/doc_test.go b/internal/backport/go/doc/doc_test.go deleted file mode 100644 index 8448fb310c..0000000000 --- a/internal/backport/go/doc/doc_test.go +++ /dev/null @@ -1,297 +0,0 @@ -// Copyright 2012 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 doc - -import ( - "bytes" - "flag" - "fmt" - "io/fs" - "os" - "path/filepath" - "regexp" - "strings" - "testing" - "text/template" - - "golang.org/x/website/internal/backport/diff" - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/parser" - "golang.org/x/website/internal/backport/go/printer" - "golang.org/x/website/internal/backport/go/token" -) - -var update = flag.Bool("update", false, "update golden (.out) files") -var files = flag.String("files", "", "consider only Go test files matching this regular expression") - -const dataDir = "testdata" - -var templateTxt = readTemplate("template.txt") - -func readTemplate(filename string) *template.Template { - t := template.New(filename) - t.Funcs(template.FuncMap{ - "node": nodeFmt, - "synopsis": synopsisFmt, - "indent": indentFmt, - }) - return template.Must(t.ParseFiles(filepath.Join(dataDir, filename))) -} - -func nodeFmt(node interface{}, fset *token.FileSet) string { - var buf bytes.Buffer - printer.Fprint(&buf, fset, node) - return strings.ReplaceAll(strings.TrimSpace(buf.String()), "\n", "\n\t") -} - -func synopsisFmt(s string) string { - const n = 64 - if len(s) > n { - // cut off excess text and go back to a word boundary - s = s[0:n] - if i := strings.LastIndexAny(s, "\t\n "); i >= 0 { - s = s[0:i] - } - s = strings.TrimSpace(s) + " ..." - } - return "// " + strings.ReplaceAll(s, "\n", " ") -} - -func indentFmt(indent, s string) string { - end := "" - if strings.HasSuffix(s, "\n") { - end = "\n" - s = s[:len(s)-1] - } - return indent + strings.ReplaceAll(s, "\n", "\n"+indent) + end -} - -func isGoFile(fi fs.FileInfo) bool { - name := fi.Name() - return !fi.IsDir() && - len(name) > 0 && name[0] != '.' && // ignore .files - filepath.Ext(name) == ".go" -} - -type bundle struct { - *Package - FSet *token.FileSet -} - -func test(t *testing.T, mode Mode) { - // determine file filter - filter := isGoFile - if *files != "" { - rx, err := regexp.Compile(*files) - if err != nil { - t.Fatal(err) - } - filter = func(fi fs.FileInfo) bool { - return isGoFile(fi) && rx.MatchString(fi.Name()) - } - } - - // get packages - fset := token.NewFileSet() - pkgs, err := parser.ParseDir(fset, dataDir, filter, parser.ParseComments) - if err != nil { - t.Fatal(err) - } - - // test packages - for _, pkg := range pkgs { - t.Run(pkg.Name, func(t *testing.T) { - importPath := dataDir + "/" + pkg.Name - var files []*ast.File - for _, f := range pkg.Files { - files = append(files, f) - } - doc, err := NewFromFiles(fset, files, importPath, mode) - if err != nil { - t.Fatal(err) - } - - // golden files always use / in filenames - canonicalize them - for i, filename := range doc.Filenames { - doc.Filenames[i] = filepath.ToSlash(filename) - } - - // print documentation - var buf bytes.Buffer - if err := templateTxt.Execute(&buf, bundle{doc, fset}); err != nil { - t.Fatal(err) - } - got := buf.Bytes() - - // update golden file if necessary - golden := filepath.Join(dataDir, fmt.Sprintf("%s.%d.golden", pkg.Name, mode)) - if *update { - err := os.WriteFile(golden, got, 0644) - if err != nil { - t.Fatal(err) - } - } - - // get golden file - want, err := os.ReadFile(golden) - if err != nil { - t.Fatal(err) - } - - // compare - if !bytes.Equal(got, want) { - t.Errorf("package %s\n\t%s", pkg.Name, diff.Diff("got", got, "want", want)) - } - }) - } -} - -func Test(t *testing.T) { - t.Run("default", func(t *testing.T) { test(t, 0) }) - t.Run("AllDecls", func(t *testing.T) { test(t, AllDecls) }) - t.Run("AllMethods", func(t *testing.T) { test(t, AllMethods) }) -} - -/* generics - -func TestFuncs(t *testing.T) { - fset := token.NewFileSet() - file, err := parser.ParseFile(fset, "funcs.go", strings.NewReader(funcsTestFile), parser.ParseComments) - if err != nil { - t.Fatal(err) - } - doc, err := NewFromFiles(fset, []*ast.File{file}, "importPath", Mode(0)) - if err != nil { - t.Fatal(err) - } - - for _, f := range doc.Funcs { - f.Decl = nil - } - for _, ty := range doc.Types { - for _, f := range ty.Funcs { - f.Decl = nil - } - for _, m := range ty.Methods { - m.Decl = nil - } - } - - compareFuncs := func(t *testing.T, msg string, got, want *Func) { - // ignore Decl and Examples - got.Decl = nil - got.Examples = nil - if !(got.Doc == want.Doc && - got.Name == want.Name && - got.Recv == want.Recv && - got.Orig == want.Orig && - got.Level == want.Level) { - t.Errorf("%s:\ngot %+v\nwant %+v", msg, got, want) - } - } - - compareSlices(t, "Funcs", doc.Funcs, funcsPackage.Funcs, compareFuncs) - compareSlices(t, "Types", doc.Types, funcsPackage.Types, func(t *testing.T, msg string, got, want *Type) { - if got.Name != want.Name { - t.Errorf("%s.Name: got %q, want %q", msg, got.Name, want.Name) - } else { - compareSlices(t, got.Name+".Funcs", got.Funcs, want.Funcs, compareFuncs) - compareSlices(t, got.Name+".Methods", got.Methods, want.Methods, compareFuncs) - } - }) -} - -func compareSlices[E interface{}](t *testing.T, name string, got, want []E, compareElem func(*testing.T, string, E, E)) { - if len(got) != len(want) { - t.Errorf("%s: got %d, want %d", name, len(got), len(want)) - } - for i := 0; i < len(got) && i < len(want); i++ { - compareElem(t, fmt.Sprintf("%s[%d]", name, i), got[i], want[i]) - } -} -*/ - -const funcsTestFile = ` -package funcs - -func F() {} - -type S1 struct { - S2 // embedded, exported - s3 // embedded, unexported -} - -func NewS1() S1 {return S1{} } -func NewS1p() *S1 { return &S1{} } - -func (S1) M1() {} -func (r S1) M2() {} -func(S1) m3() {} // unexported not shown -func (*S1) P1() {} // pointer receiver - -type S2 int -func (S2) M3() {} // shown on S2 - -type s3 int -func (s3) M4() {} // shown on S1 - -type G1[T interface{}] struct { - *s3 -} - -func NewG1[T interface{}]() G1[T] { return G1[T]{} } - -func (G1[T]) MG1() {} -func (*G1[U]) MG2() {} - -type G2[T, U interface{}] struct {} - -func NewG2[T, U interface{}]() G2[T, U] { return G2[T, U]{} } - -func (G2[T, U]) MG3() {} -func (*G2[A, B]) MG4() {} - - -` - -var funcsPackage = &Package{ - Funcs: []*Func{{Name: "F"}}, - Types: []*Type{ - { - Name: "G1", - Funcs: []*Func{{Name: "NewG1"}}, - Methods: []*Func{ - {Name: "M4", Recv: "G1", // TODO: synthesize a param for G1? - Orig: "s3", Level: 1}, - {Name: "MG1", Recv: "G1[T]", Orig: "G1[T]", Level: 0}, - {Name: "MG2", Recv: "*G1[U]", Orig: "*G1[U]", Level: 0}, - }, - }, - { - Name: "G2", - Funcs: []*Func{{Name: "NewG2"}}, - Methods: []*Func{ - {Name: "MG3", Recv: "G2[T, U]", Orig: "G2[T, U]", Level: 0}, - {Name: "MG4", Recv: "*G2[A, B]", Orig: "*G2[A, B]", Level: 0}, - }, - }, - { - Name: "S1", - Funcs: []*Func{{Name: "NewS1"}, {Name: "NewS1p"}}, - Methods: []*Func{ - {Name: "M1", Recv: "S1", Orig: "S1", Level: 0}, - {Name: "M2", Recv: "S1", Orig: "S1", Level: 0}, - {Name: "M4", Recv: "S1", Orig: "s3", Level: 1}, - {Name: "P1", Recv: "*S1", Orig: "*S1", Level: 0}, - }, - }, - { - Name: "S2", - Methods: []*Func{ - {Name: "M3", Recv: "S2", Orig: "S2", Level: 0}, - }, - }, - }, -} diff --git a/internal/backport/go/doc/example.go b/internal/backport/go/doc/example.go deleted file mode 100644 index 959c4f243f..0000000000 --- a/internal/backport/go/doc/example.go +++ /dev/null @@ -1,723 +0,0 @@ -// Copyright 2011 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. - -// Extract example functions from file ASTs. - -package doc - -import ( - "path" - "regexp" - "sort" - "strconv" - "strings" - "unicode" - "unicode/utf8" - - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/token" -) - -// An Example represents an example function found in a test source file. -type Example struct { - Name string // name of the item being exemplified (including optional suffix) - Suffix string // example suffix, without leading '_' (only populated by NewFromFiles) - Doc string // example function doc string - Code ast.Node - Play *ast.File // a whole program version of the example - Comments []*ast.CommentGroup - Output string // expected output - Unordered bool - EmptyOutput bool // expect empty output - Order int // original source code order -} - -// Examples returns the examples found in testFiles, sorted by Name field. -// The Order fields record the order in which the examples were encountered. -// The Suffix field is not populated when Examples is called directly, it is -// only populated by NewFromFiles for examples it finds in _test.go files. -// -// Playable Examples must be in a package whose name ends in "_test". -// An Example is "playable" (the Play field is non-nil) in either of these -// circumstances: -// - The example function is self-contained: the function references only -// identifiers from other packages (or predeclared identifiers, such as -// "int") and the test file does not include a dot import. -// - The entire test file is the example: the file contains exactly one -// example function, zero test, fuzz test, or benchmark function, and at -// least one top-level function, type, variable, or constant declaration -// other than the example function. -func Examples(testFiles ...*ast.File) []*Example { - var list []*Example - for _, file := range testFiles { - hasTests := false // file contains tests, fuzz test, or benchmarks - numDecl := 0 // number of non-import declarations in the file - var flist []*Example - for _, decl := range file.Decls { - if g, ok := decl.(*ast.GenDecl); ok && g.Tok != token.IMPORT { - numDecl++ - continue - } - f, ok := decl.(*ast.FuncDecl) - if !ok || f.Recv != nil { - continue - } - numDecl++ - name := f.Name.Name - if isTest(name, "Test") || isTest(name, "Benchmark") || isTest(name, "Fuzz") { - hasTests = true - continue - } - if !isTest(name, "Example") { - continue - } - if params := f.Type.Params; len(params.List) != 0 { - continue // function has params; not a valid example - } - if f.Body == nil { // ast.File.Body nil dereference (see issue 28044) - continue - } - var doc string - if f.Doc != nil { - doc = f.Doc.Text() - } - output, unordered, hasOutput := exampleOutput(f.Body, file.Comments) - flist = append(flist, &Example{ - Name: name[len("Example"):], - Doc: doc, - Code: f.Body, - Play: playExample(file, f), - Comments: file.Comments, - Output: output, - Unordered: unordered, - EmptyOutput: output == "" && hasOutput, - Order: len(flist), - }) - } - if !hasTests && numDecl > 1 && len(flist) == 1 { - // If this file only has one example function, some - // other top-level declarations, and no tests or - // benchmarks, use the whole file as the example. - flist[0].Code = file - flist[0].Play = playExampleFile(file) - } - list = append(list, flist...) - } - // sort by name - sort.Slice(list, func(i, j int) bool { - return list[i].Name < list[j].Name - }) - return list -} - -var outputPrefix = regexp.MustCompile(`(?i)^[[:space:]]*(unordered )?output:`) - -// Extracts the expected output and whether there was a valid output comment -func exampleOutput(b *ast.BlockStmt, comments []*ast.CommentGroup) (output string, unordered, ok bool) { - if _, last := lastComment(b, comments); last != nil { - // test that it begins with the correct prefix - text := last.Text() - if loc := outputPrefix.FindStringSubmatchIndex(text); loc != nil { - if loc[2] != -1 { - unordered = true - } - text = text[loc[1]:] - // Strip zero or more spaces followed by \n or a single space. - text = strings.TrimLeft(text, " ") - if len(text) > 0 && text[0] == '\n' { - text = text[1:] - } - return text, unordered, true - } - } - return "", false, false // no suitable comment found -} - -// isTest tells whether name looks like a test, example, fuzz test, or -// benchmark. It is a Test (say) if there is a character after Test that is not -// a lower-case letter. (We don't want Testiness.) -func isTest(name, prefix string) bool { - if !strings.HasPrefix(name, prefix) { - return false - } - if len(name) == len(prefix) { // "Test" is ok - return true - } - rune, _ := utf8.DecodeRuneInString(name[len(prefix):]) - return !unicode.IsLower(rune) -} - -// playExample synthesizes a new *ast.File based on the provided -// file with the provided function body as the body of main. -func playExample(file *ast.File, f *ast.FuncDecl) *ast.File { - body := f.Body - - if !strings.HasSuffix(file.Name.Name, "_test") { - // We don't support examples that are part of the - // greater package (yet). - return nil - } - - // Collect top-level declarations in the file. - topDecls := make(map[*ast.Object]ast.Decl) - typMethods := make(map[string][]ast.Decl) - - for _, decl := range file.Decls { - switch d := decl.(type) { - case *ast.FuncDecl: - if d.Recv == nil { - topDecls[d.Name.Obj] = d - } else { - if len(d.Recv.List) == 1 { - t := d.Recv.List[0].Type - tname, _ := baseTypeName(t) - typMethods[tname] = append(typMethods[tname], d) - } - } - case *ast.GenDecl: - for _, spec := range d.Specs { - switch s := spec.(type) { - case *ast.TypeSpec: - topDecls[s.Name.Obj] = d - case *ast.ValueSpec: - for _, name := range s.Names { - topDecls[name.Obj] = d - } - } - } - } - } - - // Find unresolved identifiers and uses of top-level declarations. - depDecls, unresolved := findDeclsAndUnresolved(body, topDecls, typMethods) - - // Remove predeclared identifiers from unresolved list. - for n := range unresolved { - if predeclaredTypes[n] || predeclaredConstants[n] || predeclaredFuncs[n] { - delete(unresolved, n) - } - } - - // Use unresolved identifiers to determine the imports used by this - // example. The heuristic assumes package names match base import - // paths for imports w/o renames (should be good enough most of the time). - var namedImports []ast.Spec - var blankImports []ast.Spec // _ imports - - // To preserve the blank lines between groups of imports, find the - // start position of each group, and assign that position to all - // imports from that group. - groupStarts := findImportGroupStarts(file.Imports) - groupStart := func(s *ast.ImportSpec) token.Pos { - for i, start := range groupStarts { - if s.Path.ValuePos < start { - return groupStarts[i-1] - } - } - return groupStarts[len(groupStarts)-1] - } - - for _, s := range file.Imports { - p, err := strconv.Unquote(s.Path.Value) - if err != nil { - continue - } - if p == "syscall/js" { - // We don't support examples that import syscall/js, - // because the package syscall/js is not available in the playground. - return nil - } - n := path.Base(p) - if s.Name != nil { - n = s.Name.Name - switch n { - case "_": - blankImports = append(blankImports, s) - continue - case ".": - // We can't resolve dot imports (yet). - return nil - } - } - if unresolved[n] { - // Copy the spec and its path to avoid modifying the original. - spec := *s - path := *s.Path - spec.Path = &path - spec.Path.ValuePos = groupStart(&spec) - namedImports = append(namedImports, &spec) - delete(unresolved, n) - } - } - - // If there are other unresolved identifiers, give up because this - // synthesized file is not going to build. - if len(unresolved) > 0 { - return nil - } - - // Include documentation belonging to blank imports. - var comments []*ast.CommentGroup - for _, s := range blankImports { - if c := s.(*ast.ImportSpec).Doc; c != nil { - comments = append(comments, c) - } - } - - // Include comments that are inside the function body. - for _, c := range file.Comments { - if body.Pos() <= c.Pos() && c.End() <= body.End() { - comments = append(comments, c) - } - } - - // Strip the "Output:" or "Unordered output:" comment and adjust body - // end position. - body, comments = stripOutputComment(body, comments) - - // Include documentation belonging to dependent declarations. - for _, d := range depDecls { - switch d := d.(type) { - case *ast.GenDecl: - if d.Doc != nil { - comments = append(comments, d.Doc) - } - case *ast.FuncDecl: - if d.Doc != nil { - comments = append(comments, d.Doc) - } - } - } - - // Synthesize import declaration. - importDecl := &ast.GenDecl{ - Tok: token.IMPORT, - Lparen: 1, // Need non-zero Lparen and Rparen so that printer - Rparen: 1, // treats this as a factored import. - } - importDecl.Specs = append(namedImports, blankImports...) - - // Synthesize main function. - funcDecl := &ast.FuncDecl{ - Name: ast.NewIdent("main"), - Type: f.Type, - Body: body, - } - - decls := make([]ast.Decl, 0, 2+len(depDecls)) - decls = append(decls, importDecl) - decls = append(decls, depDecls...) - decls = append(decls, funcDecl) - - sort.Slice(decls, func(i, j int) bool { - return decls[i].Pos() < decls[j].Pos() - }) - sort.Slice(comments, func(i, j int) bool { - return comments[i].Pos() < comments[j].Pos() - }) - - // Synthesize file. - return &ast.File{ - Name: ast.NewIdent("main"), - Decls: decls, - Comments: comments, - } -} - -// findDeclsAndUnresolved returns all the top-level declarations mentioned in -// the body, and a set of unresolved symbols (those that appear in the body but -// have no declaration in the program). -// -// topDecls maps objects to the top-level declaration declaring them (not -// necessarily obj.Decl, as obj.Decl will be a Spec for GenDecls, but -// topDecls[obj] will be the GenDecl itself). -func findDeclsAndUnresolved(body ast.Node, topDecls map[*ast.Object]ast.Decl, typMethods map[string][]ast.Decl) ([]ast.Decl, map[string]bool) { - // This function recursively finds every top-level declaration used - // transitively by the body, populating usedDecls and usedObjs. Then it - // trims down the declarations to include only the symbols actually - // referenced by the body. - - unresolved := make(map[string]bool) - var depDecls []ast.Decl - usedDecls := make(map[ast.Decl]bool) // set of top-level decls reachable from the body - usedObjs := make(map[*ast.Object]bool) // set of objects reachable from the body (each declared by a usedDecl) - - var inspectFunc func(ast.Node) bool - inspectFunc = func(n ast.Node) bool { - switch e := n.(type) { - case *ast.Ident: - if e.Obj == nil && e.Name != "_" { - unresolved[e.Name] = true - } else if d := topDecls[e.Obj]; d != nil { - - usedObjs[e.Obj] = true - if !usedDecls[d] { - usedDecls[d] = true - depDecls = append(depDecls, d) - } - } - return true - case *ast.SelectorExpr: - // For selector expressions, only inspect the left hand side. - // (For an expression like fmt.Println, only add "fmt" to the - // set of unresolved names, not "Println".) - ast.Inspect(e.X, inspectFunc) - return false - case *ast.KeyValueExpr: - // For key value expressions, only inspect the value - // as the key should be resolved by the type of the - // composite literal. - ast.Inspect(e.Value, inspectFunc) - return false - } - return true - } - - inspectFieldList := func(fl *ast.FieldList) { - if fl != nil { - for _, f := range fl.List { - ast.Inspect(f.Type, inspectFunc) - } - } - } - - // Find the decls immediately referenced by body. - ast.Inspect(body, inspectFunc) - // Now loop over them, adding to the list when we find a new decl that the - // body depends on. Keep going until we don't find anything new. - for i := 0; i < len(depDecls); i++ { - switch d := depDecls[i].(type) { - case *ast.FuncDecl: - // Inpect type parameters. - inspectFieldList(d.Type.TypeParams) - // Inspect types of parameters and results. See #28492. - inspectFieldList(d.Type.Params) - inspectFieldList(d.Type.Results) - - // Functions might not have a body. See #42706. - if d.Body != nil { - ast.Inspect(d.Body, inspectFunc) - } - case *ast.GenDecl: - for _, spec := range d.Specs { - switch s := spec.(type) { - case *ast.TypeSpec: - inspectFieldList(s.TypeParams) - ast.Inspect(s.Type, inspectFunc) - depDecls = append(depDecls, typMethods[s.Name.Name]...) - case *ast.ValueSpec: - if s.Type != nil { - ast.Inspect(s.Type, inspectFunc) - } - for _, val := range s.Values { - ast.Inspect(val, inspectFunc) - } - } - } - } - } - - // Some decls include multiple specs, such as a variable declaration with - // multiple variables on the same line, or a parenthesized declaration. Trim - // the declarations to include only the specs that are actually mentioned. - // However, if there is a constant group with iota, leave it all: later - // constant declarations in the group may have no value and so cannot stand - // on their own, and removing any constant from the group could change the - // values of subsequent ones. - // See testdata/examples/iota.go for a minimal example. - var ds []ast.Decl - for _, d := range depDecls { - switch d := d.(type) { - case *ast.FuncDecl: - ds = append(ds, d) - case *ast.GenDecl: - containsIota := false // does any spec have iota? - // Collect all Specs that were mentioned in the example. - var specs []ast.Spec - for _, s := range d.Specs { - switch s := s.(type) { - case *ast.TypeSpec: - if usedObjs[s.Name.Obj] { - specs = append(specs, s) - } - case *ast.ValueSpec: - if !containsIota { - containsIota = hasIota(s) - } - // A ValueSpec may have multiple names (e.g. "var a, b int"). - // Keep only the names that were mentioned in the example. - // Exception: the multiple names have a single initializer (which - // would be a function call with multiple return values). In that - // case, keep everything. - if len(s.Names) > 1 && len(s.Values) == 1 { - specs = append(specs, s) - continue - } - ns := *s - ns.Names = nil - ns.Values = nil - for i, n := range s.Names { - if usedObjs[n.Obj] { - ns.Names = append(ns.Names, n) - if s.Values != nil { - ns.Values = append(ns.Values, s.Values[i]) - } - } - } - if len(ns.Names) > 0 { - specs = append(specs, &ns) - } - } - } - if len(specs) > 0 { - // Constant with iota? Keep it all. - if d.Tok == token.CONST && containsIota { - ds = append(ds, d) - } else { - // Synthesize a GenDecl with just the Specs we need. - nd := *d // copy the GenDecl - nd.Specs = specs - if len(specs) == 1 { - // Remove grouping parens if there is only one spec. - nd.Lparen = 0 - } - ds = append(ds, &nd) - } - } - } - } - return ds, unresolved -} - -func hasIota(s ast.Spec) bool { - has := false - ast.Inspect(s, func(n ast.Node) bool { - // Check that this is the special built-in "iota" identifier, not - // a user-defined shadow. - if id, ok := n.(*ast.Ident); ok && id.Name == "iota" && id.Obj == nil { - has = true - return false - } - return true - }) - return has -} - -// findImportGroupStarts finds the start positions of each sequence of import -// specs that are not separated by a blank line. -func findImportGroupStarts(imps []*ast.ImportSpec) []token.Pos { - startImps := findImportGroupStarts1(imps) - groupStarts := make([]token.Pos, len(startImps)) - for i, imp := range startImps { - groupStarts[i] = imp.Pos() - } - return groupStarts -} - -// Helper for findImportGroupStarts to ease testing. -func findImportGroupStarts1(origImps []*ast.ImportSpec) []*ast.ImportSpec { - // Copy to avoid mutation. - imps := make([]*ast.ImportSpec, len(origImps)) - copy(imps, origImps) - // Assume the imports are sorted by position. - sort.Slice(imps, func(i, j int) bool { return imps[i].Pos() < imps[j].Pos() }) - // Assume gofmt has been applied, so there is a blank line between adjacent imps - // if and only if they are more than 2 positions apart (newline, tab). - var groupStarts []*ast.ImportSpec - prevEnd := token.Pos(-2) - for _, imp := range imps { - if imp.Pos()-prevEnd > 2 { - groupStarts = append(groupStarts, imp) - } - prevEnd = imp.End() - // Account for end-of-line comments. - if imp.Comment != nil { - prevEnd = imp.Comment.End() - } - } - return groupStarts -} - -// playExampleFile takes a whole file example and synthesizes a new *ast.File -// such that the example is function main in package main. -func playExampleFile(file *ast.File) *ast.File { - // Strip copyright comment if present. - comments := file.Comments - if len(comments) > 0 && strings.HasPrefix(comments[0].Text(), "Copyright") { - comments = comments[1:] - } - - // Copy declaration slice, rewriting the ExampleX function to main. - var decls []ast.Decl - for _, d := range file.Decls { - if f, ok := d.(*ast.FuncDecl); ok && isTest(f.Name.Name, "Example") { - // Copy the FuncDecl, as it may be used elsewhere. - newF := *f - newF.Name = ast.NewIdent("main") - newF.Body, comments = stripOutputComment(f.Body, comments) - d = &newF - } - decls = append(decls, d) - } - - // Copy the File, as it may be used elsewhere. - f := *file - f.Name = ast.NewIdent("main") - f.Decls = decls - f.Comments = comments - return &f -} - -// stripOutputComment finds and removes the "Output:" or "Unordered output:" -// comment from body and comments, and adjusts the body block's end position. -func stripOutputComment(body *ast.BlockStmt, comments []*ast.CommentGroup) (*ast.BlockStmt, []*ast.CommentGroup) { - // Do nothing if there is no "Output:" or "Unordered output:" comment. - i, last := lastComment(body, comments) - if last == nil || !outputPrefix.MatchString(last.Text()) { - return body, comments - } - - // Copy body and comments, as the originals may be used elsewhere. - newBody := &ast.BlockStmt{ - Lbrace: body.Lbrace, - List: body.List, - Rbrace: last.Pos(), - } - newComments := make([]*ast.CommentGroup, len(comments)-1) - copy(newComments, comments[:i]) - copy(newComments[i:], comments[i+1:]) - return newBody, newComments -} - -// lastComment returns the last comment inside the provided block. -func lastComment(b *ast.BlockStmt, c []*ast.CommentGroup) (i int, last *ast.CommentGroup) { - if b == nil { - return - } - pos, end := b.Pos(), b.End() - for j, cg := range c { - if cg.Pos() < pos { - continue - } - if cg.End() > end { - break - } - i, last = j, cg - } - return -} - -// classifyExamples classifies examples and assigns them to the Examples field -// of the relevant Func, Type, or Package that the example is associated with. -// -// The classification process is ambiguous in some cases: -// -// - ExampleFoo_Bar matches a type named Foo_Bar -// or a method named Foo.Bar. -// - ExampleFoo_bar matches a type named Foo_bar -// or Foo (with a "bar" suffix). -// -// Examples with malformed names are not associated with anything. -func classifyExamples(p *Package, examples []*Example) { - if len(examples) == 0 { - return - } - // Mapping of names for funcs, types, and methods to the example listing. - ids := make(map[string]*[]*Example) - ids[""] = &p.Examples // package-level examples have an empty name - for _, f := range p.Funcs { - if !token.IsExported(f.Name) { - continue - } - ids[f.Name] = &f.Examples - } - for _, t := range p.Types { - if !token.IsExported(t.Name) { - continue - } - ids[t.Name] = &t.Examples - for _, f := range t.Funcs { - if !token.IsExported(f.Name) { - continue - } - ids[f.Name] = &f.Examples - } - for _, m := range t.Methods { - if !token.IsExported(m.Name) { - continue - } - ids[strings.TrimPrefix(nameWithoutInst(m.Recv), "*")+"_"+m.Name] = &m.Examples - } - } - - // Group each example with the associated func, type, or method. - for _, ex := range examples { - // Consider all possible split points for the suffix - // by starting at the end of string (no suffix case), - // then trying all positions that contain a '_' character. - // - // An association is made on the first successful match. - // Examples with malformed names that match nothing are skipped. - for i := len(ex.Name); i >= 0; i = strings.LastIndexByte(ex.Name[:i], '_') { - prefix, suffix, ok := splitExampleName(ex.Name, i) - if !ok { - continue - } - exs, ok := ids[prefix] - if !ok { - continue - } - ex.Suffix = suffix - *exs = append(*exs, ex) - break - } - } - - // Sort list of example according to the user-specified suffix name. - for _, exs := range ids { - sort.Slice((*exs), func(i, j int) bool { - return (*exs)[i].Suffix < (*exs)[j].Suffix - }) - } -} - -// nameWithoutInst returns name if name has no brackets. If name contains -// brackets, then it returns name with all the contents between (and including) -// the outermost left and right bracket removed. -// -// Adapted from debug/gosym/symtab.go:Sym.nameWithoutInst. -func nameWithoutInst(name string) string { - start := strings.Index(name, "[") - if start < 0 { - return name - } - end := strings.LastIndex(name, "]") - if end < 0 { - // Malformed name, should contain closing bracket too. - return name - } - return name[0:start] + name[end+1:] -} - -// splitExampleName attempts to split example name s at index i, -// and reports if that produces a valid split. The suffix may be -// absent. Otherwise, it must start with a lower-case letter and -// be preceded by '_'. -// -// One of i == len(s) or s[i] == '_' must be true. -func splitExampleName(s string, i int) (prefix, suffix string, ok bool) { - if i == len(s) { - return s, "", true - } - if i == len(s)-1 { - return "", "", false - } - prefix, suffix = s[:i], s[i+1:] - return prefix, suffix, isExampleSuffix(suffix) -} - -func isExampleSuffix(s string) bool { - r, size := utf8.DecodeRuneInString(s) - return size > 0 && unicode.IsLower(r) -} diff --git a/internal/backport/go/doc/example_internal_test.go b/internal/backport/go/doc/example_internal_test.go deleted file mode 100644 index 82ce982f72..0000000000 --- a/internal/backport/go/doc/example_internal_test.go +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2022 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 doc - -import ( - "golang.org/x/website/internal/backport/go/parser" - "golang.org/x/website/internal/backport/go/token" - "reflect" - "strconv" - "strings" - "testing" -) - -func TestImportGroupStarts(t *testing.T) { - for _, test := range []struct { - name string - in string - want []string // paths of group-starting imports - }{ - { - name: "one group", - in: `package p -import ( - "a" - "b" - "c" - "d" -) -`, - want: []string{"a"}, - }, - { - name: "several groups", - in: `package p -import ( - "a" - - "b" - "c" - - "d" -) -`, - want: []string{"a", "b", "d"}, - }, - { - name: "extra space", - in: `package p -import ( - "a" - - - "b" - "c" - - - "d" -) -`, - want: []string{"a", "b", "d"}, - }, - { - name: "line comment", - in: `package p -import ( - "a" // comment - "b" // comment - - "c" -)`, - want: []string{"a", "c"}, - }, - { - name: "named import", - in: `package p -import ( - "a" - n "b" - - m "c" - "d" -)`, - want: []string{"a", "c"}, - }, - { - name: "blank import", - in: `package p -import ( - "a" - - _ "b" - - _ "c" - "d" -)`, - want: []string{"a", "b", "c"}, - }, - } { - t.Run(test.name, func(t *testing.T) { - fset := token.NewFileSet() - file, err := parser.ParseFile(fset, "test.go", strings.NewReader(test.in), parser.ParseComments) - if err != nil { - t.Fatal(err) - } - imps := findImportGroupStarts1(file.Imports) - got := make([]string, len(imps)) - for i, imp := range imps { - got[i], err = strconv.Unquote(imp.Path.Value) - if err != nil { - t.Fatal(err) - } - } - if !reflect.DeepEqual(got, test.want) { - t.Errorf("got %v, want %v", got, test.want) - } - }) - } - -} diff --git a/internal/backport/go/doc/example_test.go b/internal/backport/go/doc/example_test.go deleted file mode 100644 index ecb0bf848a..0000000000 --- a/internal/backport/go/doc/example_test.go +++ /dev/null @@ -1,344 +0,0 @@ -// Copyright 2013 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 doc_test - -import ( - "bytes" - "fmt" - "path/filepath" - "reflect" - "strings" - "testing" - - "golang.org/x/tools/txtar" - "golang.org/x/website/internal/backport/diff" - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/doc" - "golang.org/x/website/internal/backport/go/format" - "golang.org/x/website/internal/backport/go/parser" - "golang.org/x/website/internal/backport/go/token" -) - -func TestExamples(t *testing.T) { - dir := filepath.Join("testdata", "examples") - filenames, err := filepath.Glob(filepath.Join(dir, "*.go")) - if err != nil { - t.Fatal(err) - } - for _, filename := range filenames { - t.Run(strings.TrimSuffix(filepath.Base(filename), ".go"), func(t *testing.T) { - fset := token.NewFileSet() - astFile, err := parser.ParseFile(fset, filename, nil, parser.ParseComments) - if err != nil { - t.Fatal(err) - } - goldenFilename := strings.TrimSuffix(filename, ".go") + ".golden" - archive, err := txtar.ParseFile(goldenFilename) - if err != nil { - t.Fatal(err) - } - golden := map[string]string{} - for _, f := range archive.Files { - golden[f.Name] = strings.TrimSpace(string(f.Data)) - } - - // Collect the results of doc.Examples in a map keyed by example name. - examples := map[string]*doc.Example{} - for _, e := range doc.Examples(astFile) { - examples[e.Name] = e - // Treat missing sections in the golden as empty. - for _, kind := range []string{"Play", "Output"} { - key := e.Name + "." + kind - if _, ok := golden[key]; !ok { - golden[key] = "" - } - } - } - - // Each section in the golden file corresponds to an example we expect - // to see. - for sectionName, want := range golden { - name, kind, found := stringsCut(sectionName, ".") - if !found { - t.Fatalf("bad section name %q, want EXAMPLE_NAME.KIND", sectionName) - } - ex := examples[name] - if ex == nil { - t.Fatalf("no example named %q", name) - } - - var got string - switch kind { - case "Play": - got = strings.TrimSpace(formatFile(t, fset, ex.Play)) - - case "Output": - got = strings.TrimSpace(ex.Output) - default: - t.Fatalf("bad section kind %q", kind) - } - - if got != want { - t.Errorf("%s mismatch:\n%s", sectionName, - diff.Diff("want", []byte(want), "got", []byte(got))) - } - } - }) - } -} - -func formatFile(t *testing.T, fset *token.FileSet, n *ast.File) string { - t.Helper() - if n == nil { - return "" - } - var buf bytes.Buffer - if err := format.Node(&buf, fset, n); err != nil { - t.Fatal(err) - } - return buf.String() -} - -// This example illustrates how to use NewFromFiles -// to compute package documentation with examples. -func ExampleNewFromFiles() { - // src and test are two source files that make up - // a package whose documentation will be computed. - const src = ` -// This is the package comment. -package p - -import "fmt" - -// This comment is associated with the Greet function. -func Greet(who string) { - fmt.Printf("Hello, %s!\n", who) -} -` - const test = ` -package p_test - -// This comment is associated with the ExampleGreet_world example. -func ExampleGreet_world() { - Greet("world") -} -` - - // Create the AST by parsing src and test. - fset := token.NewFileSet() - files := []*ast.File{ - mustParse(fset, "src.go", src), - mustParse(fset, "src_test.go", test), - } - - // Compute package documentation with examples. - p, err := doc.NewFromFiles(fset, files, "example.com/p") - if err != nil { - panic(err) - } - - fmt.Printf("package %s - %s", p.Name, p.Doc) - fmt.Printf("func %s - %s", p.Funcs[0].Name, p.Funcs[0].Doc) - fmt.Printf(" ⤷ example with suffix %q - %s", p.Funcs[0].Examples[0].Suffix, p.Funcs[0].Examples[0].Doc) - - // Output: - // package p - This is the package comment. - // func Greet - This comment is associated with the Greet function. - // ⤷ example with suffix "world" - This comment is associated with the ExampleGreet_world example. -} - -func TestClassifyExamples(t *testing.T) { - const src = ` -package p - -const Const1 = 0 -var Var1 = 0 - -type ( - Type1 int - Type1_Foo int - Type1_foo int - type2 int - - Embed struct { Type1 } - Uembed struct { type2 } -) - -func Func1() {} -func Func1_Foo() {} -func Func1_foo() {} -func func2() {} - -func (Type1) Func1() {} -func (Type1) Func1_Foo() {} -func (Type1) Func1_foo() {} -func (Type1) func2() {} - -func (type2) Func1() {} - -type ( - Conflict int - Conflict_Conflict int - Conflict_conflict int -) - -func (Conflict) Conflict() {} - -func GFunc[T interface{}]() {} - -type GType[T interface{}] int - -func (GType[T]) M() {} -` - const test = ` -package p_test - -func ExampleConst1() {} // invalid - no support for consts and vars -func ExampleVar1() {} // invalid - no support for consts and vars - -func Example() {} -func Example_() {} // invalid - suffix must start with a lower-case letter -func Example_suffix() {} -func Example_suffix_xX_X_x() {} -func Example_世界() {} // invalid - suffix must start with a lower-case letter -func Example_123() {} // invalid - suffix must start with a lower-case letter -func Example_BadSuffix() {} // invalid - suffix must start with a lower-case letter - -func ExampleType1() {} -func ExampleType1_() {} // invalid - suffix must start with a lower-case letter -func ExampleType1_suffix() {} -func ExampleType1_BadSuffix() {} // invalid - suffix must start with a lower-case letter -func ExampleType1_Foo() {} -func ExampleType1_Foo_suffix() {} -func ExampleType1_Foo_BadSuffix() {} // invalid - suffix must start with a lower-case letter -func ExampleType1_foo() {} -func ExampleType1_foo_suffix() {} -func ExampleType1_foo_Suffix() {} // matches Type1, instead of Type1_foo -func Exampletype2() {} // invalid - cannot match unexported - -func ExampleFunc1() {} -func ExampleFunc1_() {} // invalid - suffix must start with a lower-case letter -func ExampleFunc1_suffix() {} -func ExampleFunc1_BadSuffix() {} // invalid - suffix must start with a lower-case letter -func ExampleFunc1_Foo() {} -func ExampleFunc1_Foo_suffix() {} -func ExampleFunc1_Foo_BadSuffix() {} // invalid - suffix must start with a lower-case letter -func ExampleFunc1_foo() {} -func ExampleFunc1_foo_suffix() {} -func ExampleFunc1_foo_Suffix() {} // matches Func1, instead of Func1_foo -func Examplefunc1() {} // invalid - cannot match unexported - -func ExampleType1_Func1() {} -func ExampleType1_Func1_() {} // invalid - suffix must start with a lower-case letter -func ExampleType1_Func1_suffix() {} -func ExampleType1_Func1_BadSuffix() {} // invalid - suffix must start with a lower-case letter -func ExampleType1_Func1_Foo() {} -func ExampleType1_Func1_Foo_suffix() {} -func ExampleType1_Func1_Foo_BadSuffix() {} // invalid - suffix must start with a lower-case letter -func ExampleType1_Func1_foo() {} -func ExampleType1_Func1_foo_suffix() {} -func ExampleType1_Func1_foo_Suffix() {} // matches Type1.Func1, instead of Type1.Func1_foo -func ExampleType1_func2() {} // matches Type1, instead of Type1.func2 - -func ExampleEmbed_Func1() {} // invalid - no support for forwarded methods from embedding exported type -func ExampleUembed_Func1() {} // methods from embedding unexported types are OK -func ExampleUembed_Func1_suffix() {} - -func ExampleConflict_Conflict() {} // ambiguous with either Conflict or Conflict_Conflict type -func ExampleConflict_conflict() {} // ambiguous with either Conflict or Conflict_conflict type -func ExampleConflict_Conflict_suffix() {} // ambiguous with either Conflict or Conflict_Conflict type -func ExampleConflict_conflict_suffix() {} // ambiguous with either Conflict or Conflict_conflict type - -func ExampleGFunc() {} -func ExampleGFunc_suffix() {} - -func ExampleGType_M() {} -func ExampleGType_M_suffix() {} -` - - // Parse literal source code as a *doc.Package. - fset := token.NewFileSet() - files := []*ast.File{ - mustParse(fset, "src.go", src), - mustParse(fset, "src_test.go", test), - } - p, err := doc.NewFromFiles(fset, files, "example.com/p") - if err != nil { - t.Fatalf("doc.NewFromFiles: %v", err) - } - - // Collect the association of examples to top-level identifiers. - got := map[string][]string{} - got[""] = exampleNames(p.Examples) - for _, f := range p.Funcs { - got[f.Name] = exampleNames(f.Examples) - } - for _, t := range p.Types { - got[t.Name] = exampleNames(t.Examples) - for _, f := range t.Funcs { - got[f.Name] = exampleNames(f.Examples) - } - for _, m := range t.Methods { - got[t.Name+"."+m.Name] = exampleNames(m.Examples) - } - } - - want := map[string][]string{ - "": {"", "suffix", "suffix_xX_X_x"}, // Package-level examples. - - "Type1": {"", "foo_Suffix", "func2", "suffix"}, - "Type1_Foo": {"", "suffix"}, - "Type1_foo": {"", "suffix"}, - - "Func1": {"", "foo_Suffix", "suffix"}, - "Func1_Foo": {"", "suffix"}, - "Func1_foo": {"", "suffix"}, - - "Type1.Func1": {"", "foo_Suffix", "suffix"}, - "Type1.Func1_Foo": {"", "suffix"}, - "Type1.Func1_foo": {"", "suffix"}, - - "Uembed.Func1": {"", "suffix"}, - - // These are implementation dependent due to the ambiguous parsing. - "Conflict_Conflict": {"", "suffix"}, - "Conflict_conflict": {"", "suffix"}, - - "GFunc": {"", "suffix"}, - "GType.M": {"", "suffix"}, - } - - for id := range got { - if !reflect.DeepEqual(got[id], want[id]) { - t.Errorf("classification mismatch for %q:\ngot %q\nwant %q", id, got[id], want[id]) - } - delete(want, id) - } - if len(want) > 0 { - t.Errorf("did not find:\n%q", want) - } -} - -func exampleNames(exs []*doc.Example) (out []string) { - for _, ex := range exs { - out = append(out, ex.Suffix) - } - return out -} - -func mustParse(fset *token.FileSet, filename, src string) *ast.File { - f, err := parser.ParseFile(fset, filename, src, parser.ParseComments) - if err != nil { - panic(err) - } - return f -} - -func stringsCut(s, sep string) (before, after string, found bool) { - if i := strings.Index(s, sep); i >= 0 { - return s[:i], s[i+len(sep):], true - } - return s, "", false -} diff --git a/internal/backport/go/doc/exports.go b/internal/backport/go/doc/exports.go deleted file mode 100644 index c2b91d5f8f..0000000000 --- a/internal/backport/go/doc/exports.go +++ /dev/null @@ -1,324 +0,0 @@ -// Copyright 2011 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. - -// This file implements export filtering of an AST. - -package doc - -import ( - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/token" -) - -// filterIdentList removes unexported names from list in place -// and returns the resulting list. -func filterIdentList(list []*ast.Ident) []*ast.Ident { - j := 0 - for _, x := range list { - if token.IsExported(x.Name) { - list[j] = x - j++ - } - } - return list[0:j] -} - -var underscore = ast.NewIdent("_") - -func filterCompositeLit(lit *ast.CompositeLit, filter Filter, export bool) { - n := len(lit.Elts) - lit.Elts = filterExprList(lit.Elts, filter, export) - if len(lit.Elts) < n { - lit.Incomplete = true - } -} - -func filterExprList(list []ast.Expr, filter Filter, export bool) []ast.Expr { - j := 0 - for _, exp := range list { - switch x := exp.(type) { - case *ast.CompositeLit: - filterCompositeLit(x, filter, export) - case *ast.KeyValueExpr: - if x, ok := x.Key.(*ast.Ident); ok && !filter(x.Name) { - continue - } - if x, ok := x.Value.(*ast.CompositeLit); ok { - filterCompositeLit(x, filter, export) - } - } - list[j] = exp - j++ - } - return list[0:j] -} - -// updateIdentList replaces all unexported identifiers with underscore -// and reports whether at least one exported name exists. -func updateIdentList(list []*ast.Ident) (hasExported bool) { - for i, x := range list { - if token.IsExported(x.Name) { - hasExported = true - } else { - list[i] = underscore - } - } - return hasExported -} - -// hasExportedName reports whether list contains any exported names. -func hasExportedName(list []*ast.Ident) bool { - for _, x := range list { - if x.IsExported() { - return true - } - } - return false -} - -// removeAnonymousField removes anonymous fields named name from an interface. -func removeAnonymousField(name string, ityp *ast.InterfaceType) { - list := ityp.Methods.List // we know that ityp.Methods != nil - j := 0 - for _, field := range list { - keepField := true - if n := len(field.Names); n == 0 { - // anonymous field - if fname, _ := baseTypeName(field.Type); fname == name { - keepField = false - } - } - if keepField { - list[j] = field - j++ - } - } - if j < len(list) { - ityp.Incomplete = true - } - ityp.Methods.List = list[0:j] -} - -// filterFieldList removes unexported fields (field names) from the field list -// in place and reports whether fields were removed. Anonymous fields are -// recorded with the parent type. filterType is called with the types of -// all remaining fields. -func (r *reader) filterFieldList(parent *namedType, fields *ast.FieldList, ityp *ast.InterfaceType) (removedFields bool) { - if fields == nil { - return - } - list := fields.List - j := 0 - for _, field := range list { - keepField := false - if n := len(field.Names); n == 0 { - // anonymous field or embedded type or union element - fname := r.recordAnonymousField(parent, field.Type) - if fname != "" { - if token.IsExported(fname) { - keepField = true - } else if ityp != nil && predeclaredTypes[fname] { - // possibly an embedded predeclared type; keep it for now but - // remember this interface so that it can be fixed if name is also - // defined locally - keepField = true - r.remember(fname, ityp) - } - } else { - // If we're operating on an interface, assume that this is an embedded - // type or union element. - // - // TODO(rfindley): consider traversing into approximation/unions - // elements to see if they are entirely unexported. - keepField = ityp != nil - } - } else { - field.Names = filterIdentList(field.Names) - if len(field.Names) < n { - removedFields = true - } - if len(field.Names) > 0 { - keepField = true - } - } - if keepField { - r.filterType(nil, field.Type) - list[j] = field - j++ - } - } - if j < len(list) { - removedFields = true - } - fields.List = list[0:j] - return -} - -// filterParamList applies filterType to each parameter type in fields. -func (r *reader) filterParamList(fields *ast.FieldList) { - if fields != nil { - for _, f := range fields.List { - r.filterType(nil, f.Type) - } - } -} - -// filterType strips any unexported struct fields or method types from typ -// in place. If fields (or methods) have been removed, the corresponding -// struct or interface type has the Incomplete field set to true. -func (r *reader) filterType(parent *namedType, typ ast.Expr) { - switch t := typ.(type) { - case *ast.Ident: - // nothing to do - case *ast.ParenExpr: - r.filterType(nil, t.X) - case *ast.StarExpr: // possibly an embedded type literal - r.filterType(nil, t.X) - case *ast.UnaryExpr: - if t.Op == token.TILDE { // approximation element - r.filterType(nil, t.X) - } - case *ast.BinaryExpr: - if t.Op == token.OR { // union - r.filterType(nil, t.X) - r.filterType(nil, t.Y) - } - case *ast.ArrayType: - r.filterType(nil, t.Elt) - case *ast.StructType: - if r.filterFieldList(parent, t.Fields, nil) { - t.Incomplete = true - } - case *ast.FuncType: - r.filterParamList(t.TypeParams) - r.filterParamList(t.Params) - r.filterParamList(t.Results) - case *ast.InterfaceType: - if r.filterFieldList(parent, t.Methods, t) { - t.Incomplete = true - } - case *ast.MapType: - r.filterType(nil, t.Key) - r.filterType(nil, t.Value) - case *ast.ChanType: - r.filterType(nil, t.Value) - } -} - -func (r *reader) filterSpec(spec ast.Spec) bool { - switch s := spec.(type) { - case *ast.ImportSpec: - // always keep imports so we can collect them - return true - case *ast.ValueSpec: - s.Values = filterExprList(s.Values, token.IsExported, true) - if len(s.Values) > 0 || s.Type == nil && len(s.Values) == 0 { - // If there are values declared on RHS, just replace the unexported - // identifiers on the LHS with underscore, so that it matches - // the sequence of expression on the RHS. - // - // Similarly, if there are no type and values, then this expression - // must be following an iota expression, where order matters. - if updateIdentList(s.Names) { - r.filterType(nil, s.Type) - return true - } - } else { - s.Names = filterIdentList(s.Names) - if len(s.Names) > 0 { - r.filterType(nil, s.Type) - return true - } - } - case *ast.TypeSpec: - // Don't filter type parameters here, by analogy with function parameters - // which are not filtered for top-level function declarations. - if name := s.Name.Name; token.IsExported(name) { - r.filterType(r.lookupType(s.Name.Name), s.Type) - return true - } else if IsPredeclared(name) { - if r.shadowedPredecl == nil { - r.shadowedPredecl = make(map[string]bool) - } - r.shadowedPredecl[name] = true - } - } - return false -} - -// copyConstType returns a copy of typ with position pos. -// typ must be a valid constant type. -// In practice, only (possibly qualified) identifiers are possible. -func copyConstType(typ ast.Expr, pos token.Pos) ast.Expr { - switch typ := typ.(type) { - case *ast.Ident: - return &ast.Ident{Name: typ.Name, NamePos: pos} - case *ast.SelectorExpr: - if id, ok := typ.X.(*ast.Ident); ok { - // presumably a qualified identifier - return &ast.SelectorExpr{ - Sel: ast.NewIdent(typ.Sel.Name), - X: &ast.Ident{Name: id.Name, NamePos: pos}, - } - } - } - return nil // shouldn't happen, but be conservative and don't panic -} - -func (r *reader) filterSpecList(list []ast.Spec, tok token.Token) []ast.Spec { - if tok == token.CONST { - // Propagate any type information that would get lost otherwise - // when unexported constants are filtered. - var prevType ast.Expr - for _, spec := range list { - spec := spec.(*ast.ValueSpec) - if spec.Type == nil && len(spec.Values) == 0 && prevType != nil { - // provide current spec with an explicit type - spec.Type = copyConstType(prevType, spec.Pos()) - } - if hasExportedName(spec.Names) { - // exported names are preserved so there's no need to propagate the type - prevType = nil - } else { - prevType = spec.Type - } - } - } - - j := 0 - for _, s := range list { - if r.filterSpec(s) { - list[j] = s - j++ - } - } - return list[0:j] -} - -func (r *reader) filterDecl(decl ast.Decl) bool { - switch d := decl.(type) { - case *ast.GenDecl: - d.Specs = r.filterSpecList(d.Specs, d.Tok) - return len(d.Specs) > 0 - case *ast.FuncDecl: - // ok to filter these methods early because any - // conflicting method will be filtered here, too - - // thus, removing these methods early will not lead - // to the false removal of possible conflicts - return token.IsExported(d.Name.Name) - } - return false -} - -// fileExports removes unexported declarations from src in place. -func (r *reader) fileExports(src *ast.File) { - j := 0 - for _, d := range src.Decls { - if r.filterDecl(d) { - src.Decls[j] = d - j++ - } - } - src.Decls = src.Decls[0:j] -} diff --git a/internal/backport/go/doc/filter.go b/internal/backport/go/doc/filter.go deleted file mode 100644 index ddf0368692..0000000000 --- a/internal/backport/go/doc/filter.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2009 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 doc - -import "golang.org/x/website/internal/backport/go/ast" - -type Filter func(string) bool - -func matchFields(fields *ast.FieldList, f Filter) bool { - if fields != nil { - for _, field := range fields.List { - for _, name := range field.Names { - if f(name.Name) { - return true - } - } - } - } - return false -} - -func matchDecl(d *ast.GenDecl, f Filter) bool { - for _, d := range d.Specs { - switch v := d.(type) { - case *ast.ValueSpec: - for _, name := range v.Names { - if f(name.Name) { - return true - } - } - case *ast.TypeSpec: - if f(v.Name.Name) { - return true - } - // We don't match ordinary parameters in filterFuncs, so by analogy don't - // match type parameters here. - switch t := v.Type.(type) { - case *ast.StructType: - if matchFields(t.Fields, f) { - return true - } - case *ast.InterfaceType: - if matchFields(t.Methods, f) { - return true - } - } - } - } - return false -} - -func filterValues(a []*Value, f Filter) []*Value { - w := 0 - for _, vd := range a { - if matchDecl(vd.Decl, f) { - a[w] = vd - w++ - } - } - return a[0:w] -} - -func filterFuncs(a []*Func, f Filter) []*Func { - w := 0 - for _, fd := range a { - if f(fd.Name) { - a[w] = fd - w++ - } - } - return a[0:w] -} - -func filterTypes(a []*Type, f Filter) []*Type { - w := 0 - for _, td := range a { - n := 0 // number of matches - if matchDecl(td.Decl, f) { - n = 1 - } else { - // type name doesn't match, but we may have matching consts, vars, factories or methods - td.Consts = filterValues(td.Consts, f) - td.Vars = filterValues(td.Vars, f) - td.Funcs = filterFuncs(td.Funcs, f) - td.Methods = filterFuncs(td.Methods, f) - n += len(td.Consts) + len(td.Vars) + len(td.Funcs) + len(td.Methods) - } - if n > 0 { - a[w] = td - w++ - } - } - return a[0:w] -} - -// Filter eliminates documentation for names that don't pass through the filter f. -// TODO(gri): Recognize "Type.Method" as a name. -func (p *Package) Filter(f Filter) { - p.Consts = filterValues(p.Consts, f) - p.Vars = filterValues(p.Vars, f) - p.Types = filterTypes(p.Types, f) - p.Funcs = filterFuncs(p.Funcs, f) - p.Doc = "" // don't show top-level package doc -} diff --git a/internal/backport/go/doc/headscan b/internal/backport/go/doc/headscan deleted file mode 100755 index 1c20463268..0000000000 Binary files a/internal/backport/go/doc/headscan and /dev/null differ diff --git a/internal/backport/go/doc/headscan.go b/internal/backport/go/doc/headscan.go deleted file mode 100644 index 7529525a02..0000000000 --- a/internal/backport/go/doc/headscan.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2011 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. - -//go:build ignore -// +build ignore - -/* -The headscan command extracts comment headings from package files; -it is used to detect false positives which may require an adjustment -to the comment formatting heuristics in comment.go. - -Usage: headscan [-root root_directory] - -By default, the $GOROOT/src directory is scanned. -*/ -package main - -import ( - "bytes" - "flag" - "fmt" - "io/fs" - "os" - "path/filepath" - "regexp" - "runtime" - "strings" - - "golang.org/x/website/internal/backport/go/doc" - "golang.org/x/website/internal/backport/go/parser" - "golang.org/x/website/internal/backport/go/token" -) - -var ( - root = flag.String("root", filepath.Join(runtime.GOROOT(), "src"), "root of filesystem tree to scan") - verbose = flag.Bool("v", false, "verbose mode") -) - -// ToHTML in comment.go assigns a (possibly blank) ID to each heading -var html_h = regexp.MustCompile(`

`) - -const html_endh = "

\n" - -func isGoFile(fi fs.FileInfo) bool { - return strings.HasSuffix(fi.Name(), ".go") && - !strings.HasSuffix(fi.Name(), "_test.go") -} - -func appendHeadings(list []string, comment string) []string { - var buf bytes.Buffer - doc.ToHTML(&buf, comment, nil) - for s := buf.String(); s != ""; { - loc := html_h.FindStringIndex(s) - if len(loc) == 0 { - break - } - var inner string - inner, s, _ = strings.Cut(s[loc[1]:], html_endh) - list = append(list, inner) - } - return list -} - -func main() { - flag.Parse() - fset := token.NewFileSet() - nheadings := 0 - err := filepath.WalkDir(*root, func(path string, info fs.DirEntry, err error) error { - if !info.IsDir() { - return nil - } - pkgs, err := parser.ParseDir(fset, path, isGoFile, parser.ParseComments) - if err != nil { - if *verbose { - fmt.Fprintln(os.Stderr, err) - } - return nil - } - for _, pkg := range pkgs { - d := doc.New(pkg, path, doc.Mode(0)) - list := appendHeadings(nil, d.Doc) - for _, d := range d.Consts { - list = appendHeadings(list, d.Doc) - } - for _, d := range d.Types { - list = appendHeadings(list, d.Doc) - } - for _, d := range d.Vars { - list = appendHeadings(list, d.Doc) - } - for _, d := range d.Funcs { - list = appendHeadings(list, d.Doc) - } - if len(list) > 0 { - // directories may contain multiple packages; - // print path and package name - fmt.Printf("%s (package %s)\n", path, pkg.Name) - for _, h := range list { - fmt.Printf("\t%s\n", h) - } - nheadings += len(list) - } - } - return nil - }) - if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - fmt.Println(nheadings, "headings found") -} diff --git a/internal/backport/go/doc/reader.go b/internal/backport/go/doc/reader.go deleted file mode 100644 index 2c9ba03762..0000000000 --- a/internal/backport/go/doc/reader.go +++ /dev/null @@ -1,1030 +0,0 @@ -// Copyright 2009 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 doc - -import ( - "fmt" - "path" - "regexp" - "sort" - "strconv" - "strings" - "unicode" - "unicode/utf8" - - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/token" -) - -// ---------------------------------------------------------------------------- -// function/method sets -// -// Internally, we treat functions like methods and collect them in method sets. - -// A methodSet describes a set of methods. Entries where Decl == nil are conflict -// entries (more than one method with the same name at the same embedding level). -type methodSet map[string]*Func - -// recvString returns a string representation of recv of the form "T", "*T", -// "T[A, ...]", "*T[A, ...]" or "BADRECV" (if not a proper receiver type). -func recvString(recv ast.Expr) string { - switch t := recv.(type) { - case *ast.Ident: - return t.Name - case *ast.StarExpr: - return "*" + recvString(t.X) - case *ast.IndexExpr: - // Generic type with one parameter. - return fmt.Sprintf("%s[%s]", recvString(t.X), recvParam(t.Index)) - case *ast.IndexListExpr: - // Generic type with multiple parameters. - if len(t.Indices) > 0 { - var b strings.Builder - b.WriteString(recvString(t.X)) - b.WriteByte('[') - b.WriteString(recvParam(t.Indices[0])) - for _, e := range t.Indices[1:] { - b.WriteString(", ") - b.WriteString(recvParam(e)) - } - b.WriteByte(']') - return b.String() - } - } - return "BADRECV" -} - -func recvParam(p ast.Expr) string { - if id, ok := p.(*ast.Ident); ok { - return id.Name - } - return "BADPARAM" -} - -// set creates the corresponding Func for f and adds it to mset. -// If there are multiple f's with the same name, set keeps the first -// one with documentation; conflicts are ignored. The boolean -// specifies whether to leave the AST untouched. -func (mset methodSet) set(f *ast.FuncDecl, preserveAST bool) { - name := f.Name.Name - if g := mset[name]; g != nil && g.Doc != "" { - // A function with the same name has already been registered; - // since it has documentation, assume f is simply another - // implementation and ignore it. This does not happen if the - // caller is using go/build.ScanDir to determine the list of - // files implementing a package. - return - } - // function doesn't exist or has no documentation; use f - recv := "" - if f.Recv != nil { - var typ ast.Expr - // be careful in case of incorrect ASTs - if list := f.Recv.List; len(list) == 1 { - typ = list[0].Type - } - recv = recvString(typ) - } - mset[name] = &Func{ - Doc: f.Doc.Text(), - Name: name, - Decl: f, - Recv: recv, - Orig: recv, - } - if !preserveAST { - f.Doc = nil // doc consumed - remove from AST - } -} - -// add adds method m to the method set; m is ignored if the method set -// already contains a method with the same name at the same or a higher -// level than m. -func (mset methodSet) add(m *Func) { - old := mset[m.Name] - if old == nil || m.Level < old.Level { - mset[m.Name] = m - return - } - if m.Level == old.Level { - // conflict - mark it using a method with nil Decl - mset[m.Name] = &Func{ - Name: m.Name, - Level: m.Level, - } - } -} - -// ---------------------------------------------------------------------------- -// Named types - -// baseTypeName returns the name of the base type of x (or "") -// and whether the type is imported or not. -func baseTypeName(x ast.Expr) (name string, imported bool) { - switch t := x.(type) { - case *ast.Ident: - return t.Name, false - case *ast.IndexExpr: - return baseTypeName(t.X) - case *ast.IndexListExpr: - return baseTypeName(t.X) - case *ast.SelectorExpr: - if _, ok := t.X.(*ast.Ident); ok { - // only possible for qualified type names; - // assume type is imported - return t.Sel.Name, true - } - case *ast.ParenExpr: - return baseTypeName(t.X) - case *ast.StarExpr: - return baseTypeName(t.X) - } - return "", false -} - -// An embeddedSet describes a set of embedded types. -type embeddedSet map[*namedType]bool - -// A namedType represents a named unqualified (package local, or possibly -// predeclared) type. The namedType for a type name is always found via -// reader.lookupType. -type namedType struct { - doc string // doc comment for type - name string // type name - decl *ast.GenDecl // nil if declaration hasn't been seen yet - - isEmbedded bool // true if this type is embedded - isStruct bool // true if this type is a struct - embedded embeddedSet // true if the embedded type is a pointer - - // associated declarations - values []*Value // consts and vars - funcs methodSet - methods methodSet -} - -// ---------------------------------------------------------------------------- -// AST reader - -// reader accumulates documentation for a single package. -// It modifies the AST: Comments (declaration documentation) -// that have been collected by the reader are set to nil -// in the respective AST nodes so that they are not printed -// twice (once when printing the documentation and once when -// printing the corresponding AST node). -type reader struct { - mode Mode - - // package properties - doc string // package documentation, if any - filenames []string - notes map[string][]*Note - - // imports - imports map[string]int - hasDotImp bool // if set, package contains a dot import - importByName map[string]string - - // declarations - values []*Value // consts and vars - order int // sort order of const and var declarations (when we can't use a name) - types map[string]*namedType - funcs methodSet - - // support for package-local shadowing of predeclared types - shadowedPredecl map[string]bool - fixmap map[string][]*ast.InterfaceType -} - -func (r *reader) isVisible(name string) bool { - return r.mode&AllDecls != 0 || token.IsExported(name) -} - -// lookupType returns the base type with the given name. -// If the base type has not been encountered yet, a new -// type with the given name but no associated declaration -// is added to the type map. -func (r *reader) lookupType(name string) *namedType { - if name == "" || name == "_" { - return nil // no type docs for anonymous types - } - if typ, found := r.types[name]; found { - return typ - } - // type not found - add one without declaration - typ := &namedType{ - name: name, - embedded: make(embeddedSet), - funcs: make(methodSet), - methods: make(methodSet), - } - r.types[name] = typ - return typ -} - -// recordAnonymousField registers fieldType as the type of an -// anonymous field in the parent type. If the field is imported -// (qualified name) or the parent is nil, the field is ignored. -// The function returns the field name. -func (r *reader) recordAnonymousField(parent *namedType, fieldType ast.Expr) (fname string) { - fname, imp := baseTypeName(fieldType) - if parent == nil || imp { - return - } - if ftype := r.lookupType(fname); ftype != nil { - ftype.isEmbedded = true - _, ptr := fieldType.(*ast.StarExpr) - parent.embedded[ftype] = ptr - } - return -} - -func (r *reader) readDoc(comment *ast.CommentGroup) { - // By convention there should be only one package comment - // but collect all of them if there are more than one. - text := comment.Text() - if r.doc == "" { - r.doc = text - return - } - r.doc += "\n" + text -} - -func (r *reader) remember(predecl string, typ *ast.InterfaceType) { - if r.fixmap == nil { - r.fixmap = make(map[string][]*ast.InterfaceType) - } - r.fixmap[predecl] = append(r.fixmap[predecl], typ) -} - -func specNames(specs []ast.Spec) []string { - names := make([]string, 0, len(specs)) // reasonable estimate - for _, s := range specs { - // s guaranteed to be an *ast.ValueSpec by readValue - for _, ident := range s.(*ast.ValueSpec).Names { - names = append(names, ident.Name) - } - } - return names -} - -// readValue processes a const or var declaration. -func (r *reader) readValue(decl *ast.GenDecl) { - // determine if decl should be associated with a type - // Heuristic: For each typed entry, determine the type name, if any. - // If there is exactly one type name that is sufficiently - // frequent, associate the decl with the respective type. - domName := "" - domFreq := 0 - prev := "" - n := 0 - for _, spec := range decl.Specs { - s, ok := spec.(*ast.ValueSpec) - if !ok { - continue // should not happen, but be conservative - } - name := "" - switch { - case s.Type != nil: - // a type is present; determine its name - if n, imp := baseTypeName(s.Type); !imp { - name = n - } - case decl.Tok == token.CONST && len(s.Values) == 0: - // no type or value is present but we have a constant declaration; - // use the previous type name (possibly the empty string) - name = prev - } - if name != "" { - // entry has a named type - if domName != "" && domName != name { - // more than one type name - do not associate - // with any type - domName = "" - break - } - domName = name - domFreq++ - } - prev = name - n++ - } - - // nothing to do w/o a legal declaration - if n == 0 { - return - } - - // determine values list with which to associate the Value for this decl - values := &r.values - const threshold = 0.75 - if domName != "" && r.isVisible(domName) && domFreq >= int(float64(len(decl.Specs))*threshold) { - // typed entries are sufficiently frequent - if typ := r.lookupType(domName); typ != nil { - values = &typ.values // associate with that type - } - } - - *values = append(*values, &Value{ - Doc: decl.Doc.Text(), - Names: specNames(decl.Specs), - Decl: decl, - order: r.order, - }) - if r.mode&PreserveAST == 0 { - decl.Doc = nil // doc consumed - remove from AST - } - // Note: It's important that the order used here is global because the cleanupTypes - // methods may move values associated with types back into the global list. If the - // order is list-specific, sorting is not deterministic because the same order value - // may appear multiple times (was bug, found when fixing #16153). - r.order++ -} - -// fields returns a struct's fields or an interface's methods. -func fields(typ ast.Expr) (list []*ast.Field, isStruct bool) { - var fields *ast.FieldList - switch t := typ.(type) { - case *ast.StructType: - fields = t.Fields - isStruct = true - case *ast.InterfaceType: - fields = t.Methods - } - if fields != nil { - list = fields.List - } - return -} - -// readType processes a type declaration. -func (r *reader) readType(decl *ast.GenDecl, spec *ast.TypeSpec) { - typ := r.lookupType(spec.Name.Name) - if typ == nil { - return // no name or blank name - ignore the type - } - - // A type should be added at most once, so typ.decl - // should be nil - if it is not, simply overwrite it. - typ.decl = decl - - // compute documentation - doc := spec.Doc - if doc == nil { - // no doc associated with the spec, use the declaration doc, if any - doc = decl.Doc - } - if r.mode&PreserveAST == 0 { - spec.Doc = nil // doc consumed - remove from AST - decl.Doc = nil // doc consumed - remove from AST - } - typ.doc = doc.Text() - - // record anonymous fields (they may contribute methods) - // (some fields may have been recorded already when filtering - // exports, but that's ok) - var list []*ast.Field - list, typ.isStruct = fields(spec.Type) - for _, field := range list { - if len(field.Names) == 0 { - r.recordAnonymousField(typ, field.Type) - } - } -} - -// isPredeclared reports whether n denotes a predeclared type. -func (r *reader) isPredeclared(n string) bool { - return predeclaredTypes[n] && r.types[n] == nil -} - -// readFunc processes a func or method declaration. -func (r *reader) readFunc(fun *ast.FuncDecl) { - // strip function body if requested. - if r.mode&PreserveAST == 0 { - fun.Body = nil - } - - // associate methods with the receiver type, if any - if fun.Recv != nil { - // method - if len(fun.Recv.List) == 0 { - // should not happen (incorrect AST); (See issue 17788) - // don't show this method - return - } - recvTypeName, imp := baseTypeName(fun.Recv.List[0].Type) - if imp { - // should not happen (incorrect AST); - // don't show this method - return - } - if typ := r.lookupType(recvTypeName); typ != nil { - typ.methods.set(fun, r.mode&PreserveAST != 0) - } - // otherwise ignore the method - // TODO(gri): There may be exported methods of non-exported types - // that can be called because of exported values (consts, vars, or - // function results) of that type. Could determine if that is the - // case and then show those methods in an appropriate section. - return - } - - // Associate factory functions with the first visible result type, as long as - // others are predeclared types. - if fun.Type.Results.NumFields() >= 1 { - var typ *namedType // type to associate the function with - numResultTypes := 0 - for _, res := range fun.Type.Results.List { - factoryType := res.Type - if t, ok := factoryType.(*ast.ArrayType); ok { - // We consider functions that return slices or arrays of type - // T (or pointers to T) as factory functions of T. - factoryType = t.Elt - } - if n, imp := baseTypeName(factoryType); !imp && r.isVisible(n) && !r.isPredeclared(n) { - if lookupTypeParam(n, fun.Type.TypeParams) != nil { - // Issue #49477: don't associate fun with its type parameter result. - // A type parameter is not a defined type. - continue - } - if t := r.lookupType(n); t != nil { - typ = t - numResultTypes++ - if numResultTypes > 1 { - break - } - } - } - } - // If there is exactly one result type, - // associate the function with that type. - if numResultTypes == 1 { - typ.funcs.set(fun, r.mode&PreserveAST != 0) - return - } - } - - // just an ordinary function - r.funcs.set(fun, r.mode&PreserveAST != 0) -} - -// lookupTypeParam searches for type parameters named name within the tparams -// field list, returning the relevant identifier if found, or nil if not. -func lookupTypeParam(name string, tparams *ast.FieldList) *ast.Ident { - if tparams == nil { - return nil - } - for _, field := range tparams.List { - for _, id := range field.Names { - if id.Name == name { - return id - } - } - } - return nil -} - -var ( - noteMarker = `([A-Z][A-Z]+)\(([^)]+)\):?` // MARKER(uid), MARKER at least 2 chars, uid at least 1 char - noteMarkerRx = regexp.MustCompile(`^[ \t]*` + noteMarker) // MARKER(uid) at text start - noteCommentRx = regexp.MustCompile(`^/[/*][ \t]*` + noteMarker) // MARKER(uid) at comment start -) - -// clean replaces each sequence of space, \r, or \t characters -// with a single space and removes any trailing and leading spaces. -func clean(s string) string { - var b []byte - p := byte(' ') - for i := 0; i < len(s); i++ { - q := s[i] - if q == '\r' || q == '\t' { - q = ' ' - } - if q != ' ' || p != ' ' { - b = append(b, q) - p = q - } - } - // remove trailing blank, if any - if n := len(b); n > 0 && p == ' ' { - b = b[0 : n-1] - } - return string(b) -} - -// readNote collects a single note from a sequence of comments. -func (r *reader) readNote(list []*ast.Comment) { - text := (&ast.CommentGroup{List: list}).Text() - if m := noteMarkerRx.FindStringSubmatchIndex(text); m != nil { - // The note body starts after the marker. - // We remove any formatting so that we don't - // get spurious line breaks/indentation when - // showing the TODO body. - body := clean(text[m[1]:]) - if body != "" { - marker := text[m[2]:m[3]] - r.notes[marker] = append(r.notes[marker], &Note{ - Pos: list[0].Pos(), - End: list[len(list)-1].End(), - UID: text[m[4]:m[5]], - Body: body, - }) - } - } -} - -// readNotes extracts notes from comments. -// A note must start at the beginning of a comment with "MARKER(uid):" -// and is followed by the note body (e.g., "// BUG(gri): fix this"). -// The note ends at the end of the comment group or at the start of -// another note in the same comment group, whichever comes first. -func (r *reader) readNotes(comments []*ast.CommentGroup) { - for _, group := range comments { - i := -1 // comment index of most recent note start, valid if >= 0 - list := group.List - for j, c := range list { - if noteCommentRx.MatchString(c.Text) { - if i >= 0 { - r.readNote(list[i:j]) - } - i = j - } - } - if i >= 0 { - r.readNote(list[i:]) - } - } -} - -// readFile adds the AST for a source file to the reader. -func (r *reader) readFile(src *ast.File) { - // add package documentation - if src.Doc != nil { - r.readDoc(src.Doc) - if r.mode&PreserveAST == 0 { - src.Doc = nil // doc consumed - remove from AST - } - } - - // add all declarations but for functions which are processed in a separate pass - for _, decl := range src.Decls { - switch d := decl.(type) { - case *ast.GenDecl: - switch d.Tok { - case token.IMPORT: - // imports are handled individually - for _, spec := range d.Specs { - if s, ok := spec.(*ast.ImportSpec); ok { - if import_, err := strconv.Unquote(s.Path.Value); err == nil { - r.imports[import_] = 1 - var name string - if s.Name != nil { - name = s.Name.Name - if name == "." { - r.hasDotImp = true - } - } - if name != "." { - if name == "" { - name = assumedPackageName(import_) - } - old, ok := r.importByName[name] - if !ok { - r.importByName[name] = import_ - } else if old != import_ && old != "" { - r.importByName[name] = "" // ambiguous - } - } - } - } - } - case token.CONST, token.VAR: - // constants and variables are always handled as a group - r.readValue(d) - case token.TYPE: - // types are handled individually - if len(d.Specs) == 1 && !d.Lparen.IsValid() { - // common case: single declaration w/o parentheses - // (if a single declaration is parenthesized, - // create a new fake declaration below, so that - // go/doc type declarations always appear w/o - // parentheses) - if s, ok := d.Specs[0].(*ast.TypeSpec); ok { - r.readType(d, s) - } - break - } - for _, spec := range d.Specs { - if s, ok := spec.(*ast.TypeSpec); ok { - // use an individual (possibly fake) declaration - // for each type; this also ensures that each type - // gets to (re-)use the declaration documentation - // if there's none associated with the spec itself - fake := &ast.GenDecl{ - Doc: d.Doc, - // don't use the existing TokPos because it - // will lead to the wrong selection range for - // the fake declaration if there are more - // than one type in the group (this affects - // src/cmd/godoc/godoc.go's posLink_urlFunc) - TokPos: s.Pos(), - Tok: token.TYPE, - Specs: []ast.Spec{s}, - } - r.readType(fake, s) - } - } - } - } - } - - // collect MARKER(...): annotations - r.readNotes(src.Comments) - if r.mode&PreserveAST == 0 { - src.Comments = nil // consumed unassociated comments - remove from AST - } -} - -func (r *reader) readPackage(pkg *ast.Package, mode Mode) { - // initialize reader - r.filenames = make([]string, len(pkg.Files)) - r.imports = make(map[string]int) - r.mode = mode - r.types = make(map[string]*namedType) - r.funcs = make(methodSet) - r.notes = make(map[string][]*Note) - r.importByName = make(map[string]string) - - // sort package files before reading them so that the - // result does not depend on map iteration order - i := 0 - for filename := range pkg.Files { - r.filenames[i] = filename - i++ - } - sort.Strings(r.filenames) - - // process files in sorted order - for _, filename := range r.filenames { - f := pkg.Files[filename] - if mode&AllDecls == 0 { - r.fileExports(f) - } - r.readFile(f) - } - - for name, path := range r.importByName { - if path == "" { - delete(r.importByName, name) - } - } - - // process functions now that we have better type information - for _, f := range pkg.Files { - for _, decl := range f.Decls { - if d, ok := decl.(*ast.FuncDecl); ok { - r.readFunc(d) - } - } - } -} - -// ---------------------------------------------------------------------------- -// Types - -func customizeRecv(f *Func, recvTypeName string, embeddedIsPtr bool, level int) *Func { - if f == nil || f.Decl == nil || f.Decl.Recv == nil || len(f.Decl.Recv.List) != 1 { - return f // shouldn't happen, but be safe - } - - // copy existing receiver field and set new type - newField := *f.Decl.Recv.List[0] - origPos := newField.Type.Pos() - _, origRecvIsPtr := newField.Type.(*ast.StarExpr) - newIdent := &ast.Ident{NamePos: origPos, Name: recvTypeName} - var typ ast.Expr = newIdent - if !embeddedIsPtr && origRecvIsPtr { - newIdent.NamePos++ // '*' is one character - typ = &ast.StarExpr{Star: origPos, X: newIdent} - } - newField.Type = typ - - // copy existing receiver field list and set new receiver field - newFieldList := *f.Decl.Recv - newFieldList.List = []*ast.Field{&newField} - - // copy existing function declaration and set new receiver field list - newFuncDecl := *f.Decl - newFuncDecl.Recv = &newFieldList - - // copy existing function documentation and set new declaration - newF := *f - newF.Decl = &newFuncDecl - newF.Recv = recvString(typ) - // the Orig field never changes - newF.Level = level - - return &newF -} - -// collectEmbeddedMethods collects the embedded methods of typ in mset. -func (r *reader) collectEmbeddedMethods(mset methodSet, typ *namedType, recvTypeName string, embeddedIsPtr bool, level int, visited embeddedSet) { - visited[typ] = true - for embedded, isPtr := range typ.embedded { - // Once an embedded type is embedded as a pointer type - // all embedded types in those types are treated like - // pointer types for the purpose of the receiver type - // computation; i.e., embeddedIsPtr is sticky for this - // embedding hierarchy. - thisEmbeddedIsPtr := embeddedIsPtr || isPtr - for _, m := range embedded.methods { - // only top-level methods are embedded - if m.Level == 0 { - mset.add(customizeRecv(m, recvTypeName, thisEmbeddedIsPtr, level)) - } - } - if !visited[embedded] { - r.collectEmbeddedMethods(mset, embedded, recvTypeName, thisEmbeddedIsPtr, level+1, visited) - } - } - delete(visited, typ) -} - -// computeMethodSets determines the actual method sets for each type encountered. -func (r *reader) computeMethodSets() { - for _, t := range r.types { - // collect embedded methods for t - if t.isStruct { - // struct - r.collectEmbeddedMethods(t.methods, t, t.name, false, 1, make(embeddedSet)) - } else { - // interface - // TODO(gri) fix this - } - } - - // For any predeclared names that are declared locally, don't treat them as - // exported fields anymore. - for predecl := range r.shadowedPredecl { - for _, ityp := range r.fixmap[predecl] { - removeAnonymousField(predecl, ityp) - } - } -} - -// cleanupTypes removes the association of functions and methods with -// types that have no declaration. Instead, these functions and methods -// are shown at the package level. It also removes types with missing -// declarations or which are not visible. -func (r *reader) cleanupTypes() { - for _, t := range r.types { - visible := r.isVisible(t.name) - predeclared := predeclaredTypes[t.name] - - if t.decl == nil && (predeclared || visible && (t.isEmbedded || r.hasDotImp)) { - // t.name is a predeclared type (and was not redeclared in this package), - // or it was embedded somewhere but its declaration is missing (because - // the AST is incomplete), or we have a dot-import (and all bets are off): - // move any associated values, funcs, and methods back to the top-level so - // that they are not lost. - // 1) move values - r.values = append(r.values, t.values...) - // 2) move factory functions - for name, f := range t.funcs { - // in a correct AST, package-level function names - // are all different - no need to check for conflicts - r.funcs[name] = f - } - // 3) move methods - if !predeclared { - for name, m := range t.methods { - // don't overwrite functions with the same name - drop them - if _, found := r.funcs[name]; !found { - r.funcs[name] = m - } - } - } - } - // remove types w/o declaration or which are not visible - if t.decl == nil || !visible { - delete(r.types, t.name) - } - } -} - -// ---------------------------------------------------------------------------- -// Sorting - -type data struct { - n int - swap func(i, j int) - less func(i, j int) bool -} - -func (d *data) Len() int { return d.n } -func (d *data) Swap(i, j int) { d.swap(i, j) } -func (d *data) Less(i, j int) bool { return d.less(i, j) } - -// sortBy is a helper function for sorting -func sortBy(less func(i, j int) bool, swap func(i, j int), n int) { - sort.Sort(&data{n, swap, less}) -} - -func sortedKeys(m map[string]int) []string { - list := make([]string, len(m)) - i := 0 - for key := range m { - list[i] = key - i++ - } - sort.Strings(list) - return list -} - -// sortingName returns the name to use when sorting d into place. -func sortingName(d *ast.GenDecl) string { - if len(d.Specs) == 1 { - if s, ok := d.Specs[0].(*ast.ValueSpec); ok { - return s.Names[0].Name - } - } - return "" -} - -func sortedValues(m []*Value, tok token.Token) []*Value { - list := make([]*Value, len(m)) // big enough in any case - i := 0 - for _, val := range m { - if val.Decl.Tok == tok { - list[i] = val - i++ - } - } - list = list[0:i] - - sortBy( - func(i, j int) bool { - if ni, nj := sortingName(list[i].Decl), sortingName(list[j].Decl); ni != nj { - return ni < nj - } - return list[i].order < list[j].order - }, - func(i, j int) { list[i], list[j] = list[j], list[i] }, - len(list), - ) - - return list -} - -func sortedTypes(m map[string]*namedType, allMethods bool) []*Type { - list := make([]*Type, len(m)) - i := 0 - for _, t := range m { - list[i] = &Type{ - Doc: t.doc, - Name: t.name, - Decl: t.decl, - Consts: sortedValues(t.values, token.CONST), - Vars: sortedValues(t.values, token.VAR), - Funcs: sortedFuncs(t.funcs, true), - Methods: sortedFuncs(t.methods, allMethods), - } - i++ - } - - sortBy( - func(i, j int) bool { return list[i].Name < list[j].Name }, - func(i, j int) { list[i], list[j] = list[j], list[i] }, - len(list), - ) - - return list -} - -func removeStar(s string) string { - if len(s) > 0 && s[0] == '*' { - return s[1:] - } - return s -} - -func sortedFuncs(m methodSet, allMethods bool) []*Func { - list := make([]*Func, len(m)) - i := 0 - for _, m := range m { - // determine which methods to include - switch { - case m.Decl == nil: - // exclude conflict entry - case allMethods, m.Level == 0, !token.IsExported(removeStar(m.Orig)): - // forced inclusion, method not embedded, or method - // embedded but original receiver type not exported - list[i] = m - i++ - } - } - list = list[0:i] - sortBy( - func(i, j int) bool { return list[i].Name < list[j].Name }, - func(i, j int) { list[i], list[j] = list[j], list[i] }, - len(list), - ) - return list -} - -// noteBodies returns a list of note body strings given a list of notes. -// This is only used to populate the deprecated Package.Bugs field. -func noteBodies(notes []*Note) []string { - var list []string - for _, n := range notes { - list = append(list, n.Body) - } - return list -} - -// ---------------------------------------------------------------------------- -// Predeclared identifiers - -// IsPredeclared reports whether s is a predeclared identifier. -func IsPredeclared(s string) bool { - return predeclaredTypes[s] || predeclaredFuncs[s] || predeclaredConstants[s] -} - -var predeclaredTypes = map[string]bool{ - "interface{}": true, - "bool": true, - "byte": true, - "comparable": true, - "complex64": true, - "complex128": true, - "error": true, - "float32": true, - "float64": true, - "int": true, - "int8": true, - "int16": true, - "int32": true, - "int64": true, - "rune": true, - "string": true, - "uint": true, - "uint8": true, - "uint16": true, - "uint32": true, - "uint64": true, - "uintptr": true, -} - -var predeclaredFuncs = map[string]bool{ - "append": true, - "cap": true, - "close": true, - "complex": true, - "copy": true, - "delete": true, - "imag": true, - "len": true, - "make": true, - "new": true, - "panic": true, - "print": true, - "println": true, - "real": true, - "recover": true, -} - -var predeclaredConstants = map[string]bool{ - "false": true, - "iota": true, - "nil": true, - "true": true, -} - -// assumedPackageName returns the assumed package name -// for a given import path. This is a copy of -// golang.org/x/tools/internal/imports.ImportPathToAssumedName. -func assumedPackageName(importPath string) string { - notIdentifier := func(ch rune) bool { - return !('a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || - '0' <= ch && ch <= '9' || - ch == '_' || - ch >= utf8.RuneSelf && (unicode.IsLetter(ch) || unicode.IsDigit(ch))) - } - - base := path.Base(importPath) - if strings.HasPrefix(base, "v") { - if _, err := strconv.Atoi(base[1:]); err == nil { - dir := path.Dir(importPath) - if dir != "." { - base = path.Base(dir) - } - } - } - base = strings.TrimPrefix(base, "go-") - if i := strings.IndexFunc(base, notIdentifier); i >= 0 { - base = base[:i] - } - return base -} diff --git a/internal/backport/go/doc/synopsis.go b/internal/backport/go/doc/synopsis.go deleted file mode 100644 index 7ac23e330d..0000000000 --- a/internal/backport/go/doc/synopsis.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2012 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 doc - -import ( - "strings" - "unicode" - - "golang.org/x/website/internal/backport/go/doc/comment" -) - -// firstSentence returns the first sentence in s. -// The sentence ends after the first period followed by space and -// not preceded by exactly one uppercase letter. -func firstSentence(s string) string { - var ppp, pp, p rune - for i, q := range s { - if q == '\n' || q == '\r' || q == '\t' { - q = ' ' - } - if q == ' ' && p == '.' && (!unicode.IsUpper(pp) || unicode.IsUpper(ppp)) { - return s[:i] - } - if p == '。' || p == '.' { - return s[:i] - } - ppp, pp, p = pp, p, q - } - return s -} - -// Synopsis returns a cleaned version of the first sentence in text. -// -// Deprecated: New programs should use [Package.Synopsis] instead, -// which handles links in text properly. -func Synopsis(text string) string { - var p Package - return p.Synopsis(text) -} - -// IllegalPrefixes is a list of lower-case prefixes that identify -// a comment as not being a doc comment. -// This helps to avoid misinterpreting the common mistake -// of a copyright notice immediately before a package statement -// as being a doc comment. -var IllegalPrefixes = []string{ - "copyright", - "all rights", - "author", -} - -// Synopsis returns a cleaned version of the first sentence in text. -// That sentence ends after the first period followed by space and not -// preceded by exactly one uppercase letter, or at the first paragraph break. -// The result string has no \n, \r, or \t characters and uses only single -// spaces between words. If text starts with any of the IllegalPrefixes, -// the result is the empty string. -func (p *Package) Synopsis(text string) string { - text = firstSentence(text) - lower := strings.ToLower(text) - for _, prefix := range IllegalPrefixes { - if strings.HasPrefix(lower, prefix) { - return "" - } - } - pr := p.Printer() - pr.TextWidth = -1 - d := p.Parser().Parse(text) - if len(d.Content) == 0 { - return "" - } - if _, ok := d.Content[0].(*comment.Paragraph); !ok { - return "" - } - d.Content = d.Content[:1] // might be blank lines, code blocks, etc in “first sentence” - return strings.TrimSpace(string(pr.Text(d))) -} diff --git a/internal/backport/go/doc/synopsis_test.go b/internal/backport/go/doc/synopsis_test.go deleted file mode 100644 index 158c734bf0..0000000000 --- a/internal/backport/go/doc/synopsis_test.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2012 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 doc - -import "testing" - -var tests = []struct { - txt string - fsl int - syn string -}{ - {"", 0, ""}, - {"foo", 3, "foo"}, - {"foo.", 4, "foo."}, - {"foo.bar", 7, "foo.bar"}, - {" foo. ", 6, "foo."}, - {" foo\t bar.\n", 12, "foo bar."}, - {" foo\t bar.\n", 12, "foo bar."}, - {"a b\n\nc\r\rd\t\t", 12, "a b"}, - {"a b\n\nc\r\rd\t\t . BLA", 15, "a b"}, - {"Package poems by T.S.Eliot. To rhyme...", 27, "Package poems by T.S.Eliot."}, - {"Package poems by T. S. Eliot. To rhyme...", 29, "Package poems by T. S. Eliot."}, - {"foo implements the foo ABI. The foo ABI is...", 27, "foo implements the foo ABI."}, - {"Package\nfoo. ..", 12, "Package foo."}, - {"P . Q.", 3, "P ."}, - {"P. Q. ", 8, "P. Q."}, - {"Package Καλημέρα κόσμε.", 36, "Package Καλημέρα κόσμε."}, - {"Package こんにちは 世界\n", 31, "Package こんにちは 世界"}, - {"Package こんにちは。世界", 26, "Package こんにちは。"}, - {"Package 안녕.世界", 17, "Package 안녕."}, - {"Package foo does bar.", 21, "Package foo does bar."}, - {"Copyright 2012 Google, Inc. Package foo does bar.", 27, ""}, - {"All Rights reserved. Package foo does bar.", 20, ""}, - {"All rights reserved. Package foo does bar.", 20, ""}, - {"Authors: foo@bar.com. Package foo does bar.", 21, ""}, - {"typically invoked as ``go tool asm'',", 37, "typically invoked as “go tool asm”,"}, -} - -func TestSynopsis(t *testing.T) { - for _, e := range tests { - fs := firstSentence(e.txt) - if fs != e.txt[:e.fsl] { - t.Errorf("firstSentence(%q) = %q, want %q", e.txt, fs, e.txt[:e.fsl]) - } - syn := Synopsis(e.txt) - if syn != e.syn { - t.Errorf("Synopsis(%q) = %q, want %q", e.txt, syn, e.syn) - } - } -} diff --git a/internal/backport/go/doc/testdata/a.0.golden b/internal/backport/go/doc/testdata/a.0.golden deleted file mode 100644 index 7e680b80b4..0000000000 --- a/internal/backport/go/doc/testdata/a.0.golden +++ /dev/null @@ -1,52 +0,0 @@ -// comment 0 comment 1 -PACKAGE a - -IMPORTPATH - testdata/a - -FILENAMES - testdata/a0.go - testdata/a1.go - -BUGS .Bugs is now deprecated, please use .Notes instead - bug0 - - bug1 - - -BUGS -BUG(uid) bug0 - -BUG(uid) bug1 - - -NOTES -NOTE(uid) - -NOTE(foo) 1 of 4 - this is the first line of note 1 - - note 1 continues on this 2nd line - - note 1 continues on this 3rd line - -NOTE(foo) 2 of 4 - -NOTE(bar) 3 of 4 - -NOTE(bar) 4 of 4 - - this is the last line of note 4 - -NOTE(bam) This note which contains a (parenthesized) subphrase - must appear in its entirety. - -NOTE(xxx) The ':' after the marker and uid is optional. - - -SECBUGS -SECBUG(uid) sec hole 0 - need to fix asap - - -TODOS -TODO(uid) todo0 - -TODO(uid) todo1 - diff --git a/internal/backport/go/doc/testdata/a.1.golden b/internal/backport/go/doc/testdata/a.1.golden deleted file mode 100644 index 7e680b80b4..0000000000 --- a/internal/backport/go/doc/testdata/a.1.golden +++ /dev/null @@ -1,52 +0,0 @@ -// comment 0 comment 1 -PACKAGE a - -IMPORTPATH - testdata/a - -FILENAMES - testdata/a0.go - testdata/a1.go - -BUGS .Bugs is now deprecated, please use .Notes instead - bug0 - - bug1 - - -BUGS -BUG(uid) bug0 - -BUG(uid) bug1 - - -NOTES -NOTE(uid) - -NOTE(foo) 1 of 4 - this is the first line of note 1 - - note 1 continues on this 2nd line - - note 1 continues on this 3rd line - -NOTE(foo) 2 of 4 - -NOTE(bar) 3 of 4 - -NOTE(bar) 4 of 4 - - this is the last line of note 4 - -NOTE(bam) This note which contains a (parenthesized) subphrase - must appear in its entirety. - -NOTE(xxx) The ':' after the marker and uid is optional. - - -SECBUGS -SECBUG(uid) sec hole 0 - need to fix asap - - -TODOS -TODO(uid) todo0 - -TODO(uid) todo1 - diff --git a/internal/backport/go/doc/testdata/a.2.golden b/internal/backport/go/doc/testdata/a.2.golden deleted file mode 100644 index 7e680b80b4..0000000000 --- a/internal/backport/go/doc/testdata/a.2.golden +++ /dev/null @@ -1,52 +0,0 @@ -// comment 0 comment 1 -PACKAGE a - -IMPORTPATH - testdata/a - -FILENAMES - testdata/a0.go - testdata/a1.go - -BUGS .Bugs is now deprecated, please use .Notes instead - bug0 - - bug1 - - -BUGS -BUG(uid) bug0 - -BUG(uid) bug1 - - -NOTES -NOTE(uid) - -NOTE(foo) 1 of 4 - this is the first line of note 1 - - note 1 continues on this 2nd line - - note 1 continues on this 3rd line - -NOTE(foo) 2 of 4 - -NOTE(bar) 3 of 4 - -NOTE(bar) 4 of 4 - - this is the last line of note 4 - -NOTE(bam) This note which contains a (parenthesized) subphrase - must appear in its entirety. - -NOTE(xxx) The ':' after the marker and uid is optional. - - -SECBUGS -SECBUG(uid) sec hole 0 - need to fix asap - - -TODOS -TODO(uid) todo0 - -TODO(uid) todo1 - diff --git a/internal/backport/go/doc/testdata/a0.go b/internal/backport/go/doc/testdata/a0.go deleted file mode 100644 index 2420c8a483..0000000000 --- a/internal/backport/go/doc/testdata/a0.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2012 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. - -// comment 0 -package a - -//BUG(uid): bug0 - -//TODO(uid): todo0 - -// A note with some spaces after it, should be ignored (watch out for -// emacs modes that remove trailing whitespace). -//NOTE(uid): - -// SECBUG(uid): sec hole 0 -// need to fix asap - -// Multiple notes may be in the same comment group and should be -// recognized individually. Notes may start in the middle of a -// comment group as long as they start at the beginning of an -// individual comment. -// -// NOTE(foo): 1 of 4 - this is the first line of note 1 -// - note 1 continues on this 2nd line -// - note 1 continues on this 3rd line -// NOTE(foo): 2 of 4 -// NOTE(bar): 3 of 4 -/* NOTE(bar): 4 of 4 */ -// - this is the last line of note 4 -// -// - -// NOTE(bam): This note which contains a (parenthesized) subphrase -// must appear in its entirety. - -// NOTE(xxx) The ':' after the marker and uid is optional. - -// NOTE(): NO uid - should not show up. -// NOTE() NO uid - should not show up. diff --git a/internal/backport/go/doc/testdata/a1.go b/internal/backport/go/doc/testdata/a1.go deleted file mode 100644 index 9fad1e09bc..0000000000 --- a/internal/backport/go/doc/testdata/a1.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2012 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. - -// comment 1 -package a - -//BUG(uid): bug1 - -//TODO(uid): todo1 - -//TODO(): ignored diff --git a/internal/backport/go/doc/testdata/b.0.golden b/internal/backport/go/doc/testdata/b.0.golden deleted file mode 100644 index c06246a7b1..0000000000 --- a/internal/backport/go/doc/testdata/b.0.golden +++ /dev/null @@ -1,74 +0,0 @@ -// -PACKAGE b - -IMPORTPATH - testdata/b - -IMPORTS - a - -FILENAMES - testdata/b.go - -CONSTANTS - // - const ( - C1 notExported = iota - C2 - - C4 - C5 - ) - - // - const C notExported = 0 - - // - const Pi = 3.14 // Pi - - -VARIABLES - // - var ( - U1, U2, U4, U5 notExported - - U7 notExported = 7 - ) - - // - var MaxInt int // MaxInt - - // - var V notExported - - // - var V1, V2, V4, V5 notExported - - -FUNCTIONS - // Associated with comparable type if AllDecls is set. - func ComparableFactory() comparable - - // - func F(x int) int - - // - func F1() notExported - - // Always under the package functions list. - func NotAFactory() int - - // Associated with uint type if AllDecls is set. - func UintFactory() uint - - -TYPES - // - type T struct{} // T - - // - var V T // v - - // - func (x *T) M() - diff --git a/internal/backport/go/doc/testdata/b.1.golden b/internal/backport/go/doc/testdata/b.1.golden deleted file mode 100644 index 2b62c3400c..0000000000 --- a/internal/backport/go/doc/testdata/b.1.golden +++ /dev/null @@ -1,89 +0,0 @@ -// -PACKAGE b - -IMPORTPATH - testdata/b - -IMPORTS - a - -FILENAMES - testdata/b.go - -CONSTANTS - // - const Pi = 3.14 // Pi - - -VARIABLES - // - var MaxInt int // MaxInt - - -FUNCTIONS - // - func F(x int) int - - // Always under the package functions list. - func NotAFactory() int - - -TYPES - // - type T struct{} // T - - // - var V T // v - - // - func (x *T) M() - - // Should only appear if AllDecls is set. - type comparable struct{} // overrides a predeclared type comparable - - // Associated with comparable type if AllDecls is set. - func ComparableFactory() comparable - - // - type notExported int - - // - const ( - C1 notExported = iota - C2 - c3 - C4 - C5 - ) - - // - const C notExported = 0 - - // - var ( - U1, U2, u3, U4, U5 notExported - u6 notExported - U7 notExported = 7 - ) - - // - var V notExported - - // - var V1, V2, v3, V4, V5 notExported - - // - func F1() notExported - - // - func f2() notExported - - // Should only appear if AllDecls is set. - type uint struct{} // overrides a predeclared type uint - - // Associated with uint type if AllDecls is set. - func UintFactory() uint - - // Associated with uint type if AllDecls is set. - func uintFactory() uint - diff --git a/internal/backport/go/doc/testdata/b.2.golden b/internal/backport/go/doc/testdata/b.2.golden deleted file mode 100644 index c06246a7b1..0000000000 --- a/internal/backport/go/doc/testdata/b.2.golden +++ /dev/null @@ -1,74 +0,0 @@ -// -PACKAGE b - -IMPORTPATH - testdata/b - -IMPORTS - a - -FILENAMES - testdata/b.go - -CONSTANTS - // - const ( - C1 notExported = iota - C2 - - C4 - C5 - ) - - // - const C notExported = 0 - - // - const Pi = 3.14 // Pi - - -VARIABLES - // - var ( - U1, U2, U4, U5 notExported - - U7 notExported = 7 - ) - - // - var MaxInt int // MaxInt - - // - var V notExported - - // - var V1, V2, V4, V5 notExported - - -FUNCTIONS - // Associated with comparable type if AllDecls is set. - func ComparableFactory() comparable - - // - func F(x int) int - - // - func F1() notExported - - // Always under the package functions list. - func NotAFactory() int - - // Associated with uint type if AllDecls is set. - func UintFactory() uint - - -TYPES - // - type T struct{} // T - - // - var V T // v - - // - func (x *T) M() - diff --git a/internal/backport/go/doc/testdata/b.go b/internal/backport/go/doc/testdata/b.go deleted file mode 100644 index 61b512bc8a..0000000000 --- a/internal/backport/go/doc/testdata/b.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2012 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 b - -import "a" - -// ---------------------------------------------------------------------------- -// Basic declarations - -const Pi = 3.14 // Pi -var MaxInt int // MaxInt -type T struct{} // T -var V T // v -func F(x int) int {} // F -func (x *T) M() {} // M - -// Corner cases: association with (presumed) predeclared types - -// Always under the package functions list. -func NotAFactory() int {} - -// Associated with uint type if AllDecls is set. -func UintFactory() uint {} - -// Associated with uint type if AllDecls is set. -func uintFactory() uint {} - -// Associated with comparable type if AllDecls is set. -func ComparableFactory() comparable {} - -// Should only appear if AllDecls is set. -type uint struct{} // overrides a predeclared type uint - -// Should only appear if AllDecls is set. -type comparable struct{} // overrides a predeclared type comparable - -// ---------------------------------------------------------------------------- -// Exported declarations associated with non-exported types must always be shown. - -type notExported int - -const C notExported = 0 - -const ( - C1 notExported = iota - C2 - c3 - C4 - C5 -) - -var V notExported -var V1, V2, v3, V4, V5 notExported - -var ( - U1, U2, u3, U4, U5 notExported - u6 notExported - U7 notExported = 7 -) - -func F1() notExported {} -func f2() notExported {} diff --git a/internal/backport/go/doc/testdata/benchmark.go b/internal/backport/go/doc/testdata/benchmark.go deleted file mode 100644 index 61b5cf1a02..0000000000 --- a/internal/backport/go/doc/testdata/benchmark.go +++ /dev/null @@ -1,295 +0,0 @@ -// Copyright 2009 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 testing - -import ( - "flag" - "fmt" - "os" - "runtime" - "time" -) - -var matchBenchmarks = flag.String("test.bench", "", "regular expression to select benchmarks to run") -var benchTime = flag.Duration("test.benchtime", 1*time.Second, "approximate run time for each benchmark") - -// An internal type but exported because it is cross-package; part of the implementation -// of go test. -type InternalBenchmark struct { - Name string - F func(b *B) -} - -// B is a type passed to Benchmark functions to manage benchmark -// timing and to specify the number of iterations to run. -type B struct { - common - N int - benchmark InternalBenchmark - bytes int64 - timerOn bool - result BenchmarkResult -} - -// StartTimer starts timing a test. This function is called automatically -// before a benchmark starts, but it can also used to resume timing after -// a call to StopTimer. -func (b *B) StartTimer() { - if !b.timerOn { - b.start = time.Now() - b.timerOn = true - } -} - -// StopTimer stops timing a test. This can be used to pause the timer -// while performing complex initialization that you don't -// want to measure. -func (b *B) StopTimer() { - if b.timerOn { - b.duration += time.Now().Sub(b.start) - b.timerOn = false - } -} - -// ResetTimer sets the elapsed benchmark time to zero. -// It does not affect whether the timer is running. -func (b *B) ResetTimer() { - if b.timerOn { - b.start = time.Now() - } - b.duration = 0 -} - -// SetBytes records the number of bytes processed in a single operation. -// If this is called, the benchmark will report ns/op and MB/s. -func (b *B) SetBytes(n int64) { b.bytes = n } - -func (b *B) nsPerOp() int64 { - if b.N <= 0 { - return 0 - } - return b.duration.Nanoseconds() / int64(b.N) -} - -// runN runs a single benchmark for the specified number of iterations. -func (b *B) runN(n int) { - // Try to get a comparable environment for each run - // by clearing garbage from previous runs. - runtime.GC() - b.N = n - b.ResetTimer() - b.StartTimer() - b.benchmark.F(b) - b.StopTimer() -} - -func min(x, y int) int { - if x > y { - return y - } - return x -} - -func max(x, y int) int { - if x < y { - return y - } - return x -} - -// roundDown10 rounds a number down to the nearest power of 10. -func roundDown10(n int) int { - var tens = 0 - // tens = floor(log_10(n)) - for n > 10 { - n = n / 10 - tens++ - } - // result = 10^tens - result := 1 - for i := 0; i < tens; i++ { - result *= 10 - } - return result -} - -// roundUp rounds x up to a number of the form [1eX, 2eX, 5eX]. -func roundUp(n int) int { - base := roundDown10(n) - if n < (2 * base) { - return 2 * base - } - if n < (5 * base) { - return 5 * base - } - return 10 * base -} - -// run times the benchmark function in a separate goroutine. -func (b *B) run() BenchmarkResult { - go b.launch() - <-b.signal - return b.result -} - -// launch launches the benchmark function. It gradually increases the number -// of benchmark iterations until the benchmark runs for a second in order -// to get a reasonable measurement. It prints timing information in this form -// -// testing.BenchmarkHello 100000 19 ns/op -// -// launch is run by the fun function as a separate goroutine. -func (b *B) launch() { - // Run the benchmark for a single iteration in case it's expensive. - n := 1 - - // Signal that we're done whether we return normally - // or by FailNow's runtime.Goexit. - defer func() { - b.signal <- b - }() - - b.runN(n) - // Run the benchmark for at least the specified amount of time. - d := *benchTime - for !b.failed && b.duration < d && n < 1e9 { - last := n - // Predict iterations/sec. - if b.nsPerOp() == 0 { - n = 1e9 - } else { - n = int(d.Nanoseconds() / b.nsPerOp()) - } - // Run more iterations than we think we'll need for a second (1.5x). - // Don't grow too fast in case we had timing errors previously. - // Be sure to run at least one more than last time. - n = max(min(n+n/2, 100*last), last+1) - // Round up to something easy to read. - n = roundUp(n) - b.runN(n) - } - b.result = BenchmarkResult{b.N, b.duration, b.bytes} -} - -// The results of a benchmark run. -type BenchmarkResult struct { - N int // The number of iterations. - T time.Duration // The total time taken. - Bytes int64 // Bytes processed in one iteration. -} - -func (r BenchmarkResult) NsPerOp() int64 { - if r.N <= 0 { - return 0 - } - return r.T.Nanoseconds() / int64(r.N) -} - -func (r BenchmarkResult) mbPerSec() float64 { - if r.Bytes <= 0 || r.T <= 0 || r.N <= 0 { - return 0 - } - return (float64(r.Bytes) * float64(r.N) / 1e6) / r.T.Seconds() -} - -func (r BenchmarkResult) String() string { - mbs := r.mbPerSec() - mb := "" - if mbs != 0 { - mb = fmt.Sprintf("\t%7.2f MB/s", mbs) - } - nsop := r.NsPerOp() - ns := fmt.Sprintf("%10d ns/op", nsop) - if r.N > 0 && nsop < 100 { - // The format specifiers here make sure that - // the ones digits line up for all three possible formats. - if nsop < 10 { - ns = fmt.Sprintf("%13.2f ns/op", float64(r.T.Nanoseconds())/float64(r.N)) - } else { - ns = fmt.Sprintf("%12.1f ns/op", float64(r.T.Nanoseconds())/float64(r.N)) - } - } - return fmt.Sprintf("%8d\t%s%s", r.N, ns, mb) -} - -// An internal function but exported because it is cross-package; part of the implementation -// of go test. -func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark) { - // If no flag was specified, don't run benchmarks. - if len(*matchBenchmarks) == 0 { - return - } - for _, Benchmark := range benchmarks { - matched, err := matchString(*matchBenchmarks, Benchmark.Name) - if err != nil { - fmt.Fprintf(os.Stderr, "testing: invalid regexp for -test.bench: %s\n", err) - os.Exit(1) - } - if !matched { - continue - } - for _, procs := range cpuList { - runtime.GOMAXPROCS(procs) - b := &B{ - common: common{ - signal: make(chan interface{}), - }, - benchmark: Benchmark, - } - benchName := Benchmark.Name - if procs != 1 { - benchName = fmt.Sprintf("%s-%d", Benchmark.Name, procs) - } - fmt.Printf("%s\t", benchName) - r := b.run() - if b.failed { - // The output could be very long here, but probably isn't. - // We print it all, regardless, because we don't want to trim the reason - // the benchmark failed. - fmt.Printf("--- FAIL: %s\n%s", benchName, b.output) - continue - } - fmt.Printf("%v\n", r) - // Unlike with tests, we ignore the -chatty flag and always print output for - // benchmarks since the output generation time will skew the results. - if len(b.output) > 0 { - b.trimOutput() - fmt.Printf("--- BENCH: %s\n%s", benchName, b.output) - } - if p := runtime.GOMAXPROCS(-1); p != procs { - fmt.Fprintf(os.Stderr, "testing: %s left GOMAXPROCS set to %d\n", benchName, p) - } - } - } -} - -// trimOutput shortens the output from a benchmark, which can be very long. -func (b *B) trimOutput() { - // The output is likely to appear multiple times because the benchmark - // is run multiple times, but at least it will be seen. This is not a big deal - // because benchmarks rarely print, but just in case, we trim it if it's too long. - const maxNewlines = 10 - for nlCount, j := 0, 0; j < len(b.output); j++ { - if b.output[j] == '\n' { - nlCount++ - if nlCount >= maxNewlines { - b.output = append(b.output[:j], "\n\t... [output truncated]\n"...) - break - } - } - } -} - -// Benchmark benchmarks a single function. Useful for creating -// custom benchmarks that do not use go test. -func Benchmark(f func(b *B)) BenchmarkResult { - b := &B{ - common: common{ - signal: make(chan interface{}), - }, - benchmark: InternalBenchmark{"", f}, - } - return b.run() -} diff --git a/internal/backport/go/doc/testdata/blank.0.golden b/internal/backport/go/doc/testdata/blank.0.golden deleted file mode 100644 index 70f2929f8a..0000000000 --- a/internal/backport/go/doc/testdata/blank.0.golden +++ /dev/null @@ -1,62 +0,0 @@ -// Package blank is a go/doc test for the handling of _. See issue ... -PACKAGE blank - -IMPORTPATH - testdata/blank - -IMPORTS - os - -FILENAMES - testdata/blank.go - -CONSTANTS - // T constants counting from unexported constants. - const ( - C1 T - C2 - - C3 - - C4 int - ) - - // Constants with a single type that is not propagated. - const ( - Default = 0644 - Useless = 0312 - WideOpen = 0777 - ) - - // Constants with an imported type that is propagated. - const ( - M1 os.FileMode - M2 - M3 - ) - - // Package constants. - const ( - I1 int - I2 - ) - - -TYPES - // S has a padding field. - type S struct { - H uint32 - - A uint8 - // contains filtered or unexported fields - } - - // - type T int - - // T constants counting from a blank constant. - const ( - T1 T - T2 - ) - diff --git a/internal/backport/go/doc/testdata/blank.1.golden b/internal/backport/go/doc/testdata/blank.1.golden deleted file mode 100644 index 8098cb6e88..0000000000 --- a/internal/backport/go/doc/testdata/blank.1.golden +++ /dev/null @@ -1,83 +0,0 @@ -// Package blank is a go/doc test for the handling of _. See issue ... -PACKAGE blank - -IMPORTPATH - testdata/blank - -IMPORTS - os - -FILENAMES - testdata/blank.go - -CONSTANTS - // T constants counting from unexported constants. - const ( - tweedledee T = iota - tweedledum - C1 - C2 - alice - C3 - redQueen int = iota - C4 - ) - - // Constants with a single type that is not propagated. - const ( - zero os.FileMode = 0 - Default = 0644 - Useless = 0312 - WideOpen = 0777 - ) - - // Constants with an imported type that is propagated. - const ( - zero os.FileMode = 0 - M1 - M2 - M3 - ) - - // Package constants. - const ( - _ int = iota - I1 - I2 - ) - - // Unexported constants counting from blank iota. See issue 9615. - const ( - _ = iota - one = iota + 1 - ) - - -VARIABLES - // - var _ = T(55) - - -FUNCTIONS - // - func _() - - -TYPES - // S has a padding field. - type S struct { - H uint32 - _ uint8 - A uint8 - } - - // - type T int - - // T constants counting from a blank constant. - const ( - _ T = iota - T1 - T2 - ) - diff --git a/internal/backport/go/doc/testdata/blank.2.golden b/internal/backport/go/doc/testdata/blank.2.golden deleted file mode 100644 index 70f2929f8a..0000000000 --- a/internal/backport/go/doc/testdata/blank.2.golden +++ /dev/null @@ -1,62 +0,0 @@ -// Package blank is a go/doc test for the handling of _. See issue ... -PACKAGE blank - -IMPORTPATH - testdata/blank - -IMPORTS - os - -FILENAMES - testdata/blank.go - -CONSTANTS - // T constants counting from unexported constants. - const ( - C1 T - C2 - - C3 - - C4 int - ) - - // Constants with a single type that is not propagated. - const ( - Default = 0644 - Useless = 0312 - WideOpen = 0777 - ) - - // Constants with an imported type that is propagated. - const ( - M1 os.FileMode - M2 - M3 - ) - - // Package constants. - const ( - I1 int - I2 - ) - - -TYPES - // S has a padding field. - type S struct { - H uint32 - - A uint8 - // contains filtered or unexported fields - } - - // - type T int - - // T constants counting from a blank constant. - const ( - T1 T - T2 - ) - diff --git a/internal/backport/go/doc/testdata/blank.go b/internal/backport/go/doc/testdata/blank.go deleted file mode 100644 index 5ea6186935..0000000000 --- a/internal/backport/go/doc/testdata/blank.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2014 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 blank is a go/doc test for the handling of _. -// See issue 5397. -package blank - -import "os" - -type T int - -// T constants counting from a blank constant. -const ( - _ T = iota - T1 - T2 -) - -// T constants counting from unexported constants. -const ( - tweedledee T = iota - tweedledum - C1 - C2 - alice - C3 - redQueen int = iota - C4 -) - -// Constants with a single type that is not propagated. -const ( - zero os.FileMode = 0 - Default = 0644 - Useless = 0312 - WideOpen = 0777 -) - -// Constants with an imported type that is propagated. -const ( - zero os.FileMode = 0 - M1 - M2 - M3 -) - -// Package constants. -const ( - _ int = iota - I1 - I2 -) - -// Unexported constants counting from blank iota. -// See issue 9615. -const ( - _ = iota - one = iota + 1 -) - -// Blanks not in doc output: - -// S has a padding field. -type S struct { - H uint32 - _ uint8 - A uint8 -} - -func _() {} - -type _ T - -var _ = T(55) diff --git a/internal/backport/go/doc/testdata/bugpara.0.golden b/internal/backport/go/doc/testdata/bugpara.0.golden deleted file mode 100644 index 5804859501..0000000000 --- a/internal/backport/go/doc/testdata/bugpara.0.golden +++ /dev/null @@ -1,20 +0,0 @@ -// -PACKAGE bugpara - -IMPORTPATH - testdata/bugpara - -FILENAMES - testdata/bugpara.go - -BUGS .Bugs is now deprecated, please use .Notes instead - Sometimes bugs have multiple paragraphs. - - Like this one. - - -BUGS -BUG(rsc) Sometimes bugs have multiple paragraphs. - - Like this one. - diff --git a/internal/backport/go/doc/testdata/bugpara.1.golden b/internal/backport/go/doc/testdata/bugpara.1.golden deleted file mode 100644 index 5804859501..0000000000 --- a/internal/backport/go/doc/testdata/bugpara.1.golden +++ /dev/null @@ -1,20 +0,0 @@ -// -PACKAGE bugpara - -IMPORTPATH - testdata/bugpara - -FILENAMES - testdata/bugpara.go - -BUGS .Bugs is now deprecated, please use .Notes instead - Sometimes bugs have multiple paragraphs. - - Like this one. - - -BUGS -BUG(rsc) Sometimes bugs have multiple paragraphs. - - Like this one. - diff --git a/internal/backport/go/doc/testdata/bugpara.2.golden b/internal/backport/go/doc/testdata/bugpara.2.golden deleted file mode 100644 index 5804859501..0000000000 --- a/internal/backport/go/doc/testdata/bugpara.2.golden +++ /dev/null @@ -1,20 +0,0 @@ -// -PACKAGE bugpara - -IMPORTPATH - testdata/bugpara - -FILENAMES - testdata/bugpara.go - -BUGS .Bugs is now deprecated, please use .Notes instead - Sometimes bugs have multiple paragraphs. - - Like this one. - - -BUGS -BUG(rsc) Sometimes bugs have multiple paragraphs. - - Like this one. - diff --git a/internal/backport/go/doc/testdata/bugpara.go b/internal/backport/go/doc/testdata/bugpara.go deleted file mode 100644 index 0360a6f667..0000000000 --- a/internal/backport/go/doc/testdata/bugpara.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2013 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 bugpara - -// BUG(rsc): Sometimes bugs have multiple paragraphs. -// -// Like this one. diff --git a/internal/backport/go/doc/testdata/c.0.golden b/internal/backport/go/doc/testdata/c.0.golden deleted file mode 100644 index e21959b195..0000000000 --- a/internal/backport/go/doc/testdata/c.0.golden +++ /dev/null @@ -1,48 +0,0 @@ -// -PACKAGE c - -IMPORTPATH - testdata/c - -IMPORTS - a - -FILENAMES - testdata/c.go - -TYPES - // A (should see this) - type A struct{} - - // B (should see this) - type B struct{} - - // C (should see this) - type C struct{} - - // D (should see this) - type D struct{} - - // E1 (should see this) - type E1 struct{} - - // E (should see this for E2 and E3) - type E2 struct{} - - // E (should see this for E2 and E3) - type E3 struct{} - - // E4 (should see this) - type E4 struct{} - - // - type T1 struct{} - - // - func (t1 *T1) M() - - // T2 must not show methods of local T1 - type T2 struct { - a.T1 // not the same as locally declared T1 - } - diff --git a/internal/backport/go/doc/testdata/c.1.golden b/internal/backport/go/doc/testdata/c.1.golden deleted file mode 100644 index e21959b195..0000000000 --- a/internal/backport/go/doc/testdata/c.1.golden +++ /dev/null @@ -1,48 +0,0 @@ -// -PACKAGE c - -IMPORTPATH - testdata/c - -IMPORTS - a - -FILENAMES - testdata/c.go - -TYPES - // A (should see this) - type A struct{} - - // B (should see this) - type B struct{} - - // C (should see this) - type C struct{} - - // D (should see this) - type D struct{} - - // E1 (should see this) - type E1 struct{} - - // E (should see this for E2 and E3) - type E2 struct{} - - // E (should see this for E2 and E3) - type E3 struct{} - - // E4 (should see this) - type E4 struct{} - - // - type T1 struct{} - - // - func (t1 *T1) M() - - // T2 must not show methods of local T1 - type T2 struct { - a.T1 // not the same as locally declared T1 - } - diff --git a/internal/backport/go/doc/testdata/c.2.golden b/internal/backport/go/doc/testdata/c.2.golden deleted file mode 100644 index e21959b195..0000000000 --- a/internal/backport/go/doc/testdata/c.2.golden +++ /dev/null @@ -1,48 +0,0 @@ -// -PACKAGE c - -IMPORTPATH - testdata/c - -IMPORTS - a - -FILENAMES - testdata/c.go - -TYPES - // A (should see this) - type A struct{} - - // B (should see this) - type B struct{} - - // C (should see this) - type C struct{} - - // D (should see this) - type D struct{} - - // E1 (should see this) - type E1 struct{} - - // E (should see this for E2 and E3) - type E2 struct{} - - // E (should see this for E2 and E3) - type E3 struct{} - - // E4 (should see this) - type E4 struct{} - - // - type T1 struct{} - - // - func (t1 *T1) M() - - // T2 must not show methods of local T1 - type T2 struct { - a.T1 // not the same as locally declared T1 - } - diff --git a/internal/backport/go/doc/testdata/c.go b/internal/backport/go/doc/testdata/c.go deleted file mode 100644 index e0f39196de..0000000000 --- a/internal/backport/go/doc/testdata/c.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2012 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 c - -import "a" - -// ---------------------------------------------------------------------------- -// Test that empty declarations don't cause problems - -const () - -type () - -var () - -// ---------------------------------------------------------------------------- -// Test that types with documentation on both, the Decl and the Spec node -// are handled correctly. - -// A (should see this) -type A struct{} - -// B (should see this) -type ( - B struct{} -) - -type ( - // C (should see this) - C struct{} -) - -// D (should not see this) -type ( - // D (should see this) - D struct{} -) - -// E (should see this for E2 and E3) -type ( - // E1 (should see this) - E1 struct{} - E2 struct{} - E3 struct{} - // E4 (should see this) - E4 struct{} -) - -// ---------------------------------------------------------------------------- -// Test that local and imported types are different when -// handling anonymous fields. - -type T1 struct{} - -func (t1 *T1) M() {} - -// T2 must not show methods of local T1 -type T2 struct { - a.T1 // not the same as locally declared T1 -} diff --git a/internal/backport/go/doc/testdata/d.0.golden b/internal/backport/go/doc/testdata/d.0.golden deleted file mode 100644 index c005199533..0000000000 --- a/internal/backport/go/doc/testdata/d.0.golden +++ /dev/null @@ -1,104 +0,0 @@ -// -PACKAGE d - -IMPORTPATH - testdata/d - -FILENAMES - testdata/d1.go - testdata/d2.go - -CONSTANTS - // CBx constants should appear before CAx constants. - const ( - CB2 = iota // before CB1 - CB1 // before CB0 - CB0 // at end - ) - - // CAx constants should appear after CBx constants. - const ( - CA2 = iota // before CA1 - CA1 // before CA0 - CA0 // at end - ) - - // C0 should be first. - const C0 = 0 - - // C1 should be second. - const C1 = 1 - - // C2 should be third. - const C2 = 2 - - // - const ( - // Single const declarations inside ()'s are considered ungrouped - // and show up in sorted order. - Cungrouped = 0 - ) - - -VARIABLES - // VBx variables should appear before VAx variables. - var ( - VB2 int // before VB1 - VB1 int // before VB0 - VB0 int // at end - ) - - // VAx variables should appear after VBx variables. - var ( - VA2 int // before VA1 - VA1 int // before VA0 - VA0 int // at end - ) - - // V0 should be first. - var V0 uintptr - - // V1 should be second. - var V1 uint - - // V2 should be third. - var V2 int - - // - var ( - // Single var declarations inside ()'s are considered ungrouped - // and show up in sorted order. - Vungrouped = 0 - ) - - -FUNCTIONS - // F0 should be first. - func F0() - - // F1 should be second. - func F1() - - // F2 should be third. - func F2() - - -TYPES - // T0 should be first. - type T0 struct{} - - // T1 should be second. - type T1 struct{} - - // T2 should be third. - type T2 struct{} - - // TG0 should be first. - type TG0 struct{} - - // TG1 should be second. - type TG1 struct{} - - // TG2 should be third. - type TG2 struct{} - diff --git a/internal/backport/go/doc/testdata/d.1.golden b/internal/backport/go/doc/testdata/d.1.golden deleted file mode 100644 index c005199533..0000000000 --- a/internal/backport/go/doc/testdata/d.1.golden +++ /dev/null @@ -1,104 +0,0 @@ -// -PACKAGE d - -IMPORTPATH - testdata/d - -FILENAMES - testdata/d1.go - testdata/d2.go - -CONSTANTS - // CBx constants should appear before CAx constants. - const ( - CB2 = iota // before CB1 - CB1 // before CB0 - CB0 // at end - ) - - // CAx constants should appear after CBx constants. - const ( - CA2 = iota // before CA1 - CA1 // before CA0 - CA0 // at end - ) - - // C0 should be first. - const C0 = 0 - - // C1 should be second. - const C1 = 1 - - // C2 should be third. - const C2 = 2 - - // - const ( - // Single const declarations inside ()'s are considered ungrouped - // and show up in sorted order. - Cungrouped = 0 - ) - - -VARIABLES - // VBx variables should appear before VAx variables. - var ( - VB2 int // before VB1 - VB1 int // before VB0 - VB0 int // at end - ) - - // VAx variables should appear after VBx variables. - var ( - VA2 int // before VA1 - VA1 int // before VA0 - VA0 int // at end - ) - - // V0 should be first. - var V0 uintptr - - // V1 should be second. - var V1 uint - - // V2 should be third. - var V2 int - - // - var ( - // Single var declarations inside ()'s are considered ungrouped - // and show up in sorted order. - Vungrouped = 0 - ) - - -FUNCTIONS - // F0 should be first. - func F0() - - // F1 should be second. - func F1() - - // F2 should be third. - func F2() - - -TYPES - // T0 should be first. - type T0 struct{} - - // T1 should be second. - type T1 struct{} - - // T2 should be third. - type T2 struct{} - - // TG0 should be first. - type TG0 struct{} - - // TG1 should be second. - type TG1 struct{} - - // TG2 should be third. - type TG2 struct{} - diff --git a/internal/backport/go/doc/testdata/d.2.golden b/internal/backport/go/doc/testdata/d.2.golden deleted file mode 100644 index c005199533..0000000000 --- a/internal/backport/go/doc/testdata/d.2.golden +++ /dev/null @@ -1,104 +0,0 @@ -// -PACKAGE d - -IMPORTPATH - testdata/d - -FILENAMES - testdata/d1.go - testdata/d2.go - -CONSTANTS - // CBx constants should appear before CAx constants. - const ( - CB2 = iota // before CB1 - CB1 // before CB0 - CB0 // at end - ) - - // CAx constants should appear after CBx constants. - const ( - CA2 = iota // before CA1 - CA1 // before CA0 - CA0 // at end - ) - - // C0 should be first. - const C0 = 0 - - // C1 should be second. - const C1 = 1 - - // C2 should be third. - const C2 = 2 - - // - const ( - // Single const declarations inside ()'s are considered ungrouped - // and show up in sorted order. - Cungrouped = 0 - ) - - -VARIABLES - // VBx variables should appear before VAx variables. - var ( - VB2 int // before VB1 - VB1 int // before VB0 - VB0 int // at end - ) - - // VAx variables should appear after VBx variables. - var ( - VA2 int // before VA1 - VA1 int // before VA0 - VA0 int // at end - ) - - // V0 should be first. - var V0 uintptr - - // V1 should be second. - var V1 uint - - // V2 should be third. - var V2 int - - // - var ( - // Single var declarations inside ()'s are considered ungrouped - // and show up in sorted order. - Vungrouped = 0 - ) - - -FUNCTIONS - // F0 should be first. - func F0() - - // F1 should be second. - func F1() - - // F2 should be third. - func F2() - - -TYPES - // T0 should be first. - type T0 struct{} - - // T1 should be second. - type T1 struct{} - - // T2 should be third. - type T2 struct{} - - // TG0 should be first. - type TG0 struct{} - - // TG1 should be second. - type TG1 struct{} - - // TG2 should be third. - type TG2 struct{} - diff --git a/internal/backport/go/doc/testdata/d1.go b/internal/backport/go/doc/testdata/d1.go deleted file mode 100644 index ebd6941958..0000000000 --- a/internal/backport/go/doc/testdata/d1.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2012 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. - -// Test cases for sort order of declarations. - -package d - -// C2 should be third. -const C2 = 2 - -// V2 should be third. -var V2 int - -// CBx constants should appear before CAx constants. -const ( - CB2 = iota // before CB1 - CB1 // before CB0 - CB0 // at end -) - -// VBx variables should appear before VAx variables. -var ( - VB2 int // before VB1 - VB1 int // before VB0 - VB0 int // at end -) - -const ( - // Single const declarations inside ()'s are considered ungrouped - // and show up in sorted order. - Cungrouped = 0 -) - -var ( - // Single var declarations inside ()'s are considered ungrouped - // and show up in sorted order. - Vungrouped = 0 -) - -// T2 should be third. -type T2 struct{} - -// Grouped types are sorted nevertheless. -type ( - // TG2 should be third. - TG2 struct{} - - // TG1 should be second. - TG1 struct{} - - // TG0 should be first. - TG0 struct{} -) - -// F2 should be third. -func F2() {} diff --git a/internal/backport/go/doc/testdata/d2.go b/internal/backport/go/doc/testdata/d2.go deleted file mode 100644 index 2f56f4fa4c..0000000000 --- a/internal/backport/go/doc/testdata/d2.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2012 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. - -// Test cases for sort order of declarations. - -package d - -// C1 should be second. -const C1 = 1 - -// C0 should be first. -const C0 = 0 - -// V1 should be second. -var V1 uint - -// V0 should be first. -var V0 uintptr - -// CAx constants should appear after CBx constants. -const ( - CA2 = iota // before CA1 - CA1 // before CA0 - CA0 // at end -) - -// VAx variables should appear after VBx variables. -var ( - VA2 int // before VA1 - VA1 int // before VA0 - VA0 int // at end -) - -// T1 should be second. -type T1 struct{} - -// T0 should be first. -type T0 struct{} - -// F1 should be second. -func F1() {} - -// F0 should be first. -func F0() {} diff --git a/internal/backport/go/doc/testdata/e.0.golden b/internal/backport/go/doc/testdata/e.0.golden deleted file mode 100644 index 6987e5867c..0000000000 --- a/internal/backport/go/doc/testdata/e.0.golden +++ /dev/null @@ -1,109 +0,0 @@ -// The package e is a go/doc test for embedded methods. -PACKAGE e - -IMPORTPATH - testdata/e - -FILENAMES - testdata/e.go - -TYPES - // T1 has no embedded (level 1) M method due to conflict. - type T1 struct { - // contains filtered or unexported fields - } - - // T2 has only M as top-level method. - type T2 struct { - // contains filtered or unexported fields - } - - // T2.M should appear as method of T2. - func (T2) M() - - // T3 has only M as top-level method. - type T3 struct { - // contains filtered or unexported fields - } - - // T3.M should appear as method of T3. - func (T3) M() - - // - type T4 struct{} - - // T4.M should appear as method of T5 only if AllMethods is set. - func (*T4) M() - - // - type T5 struct { - T4 - } - - // - type U1 struct { - *U1 - } - - // U1.M should appear as method of U1. - func (*U1) M() - - // - type U2 struct { - *U3 - } - - // U2.M should appear as method of U2 and as method of U3 only if ... - func (*U2) M() - - // - type U3 struct { - *U2 - } - - // U3.N should appear as method of U3 and as method of U2 only if ... - func (*U3) N() - - // - type U4 struct { - // contains filtered or unexported fields - } - - // U4.M should appear as method of U4. - func (*U4) M() - - // - type V1 struct { - *V2 - *V5 - } - - // - type V2 struct { - *V3 - } - - // - type V3 struct { - *V4 - } - - // - type V4 struct { - *V5 - } - - // V4.M should appear as method of V2 and V3 if AllMethods is set. - func (*V4) M() - - // - type V5 struct { - *V6 - } - - // - type V6 struct{} - - // V6.M should appear as method of V1 and V5 if AllMethods is set. - func (*V6) M() - diff --git a/internal/backport/go/doc/testdata/e.1.golden b/internal/backport/go/doc/testdata/e.1.golden deleted file mode 100644 index cbe22e0bf6..0000000000 --- a/internal/backport/go/doc/testdata/e.1.golden +++ /dev/null @@ -1,144 +0,0 @@ -// The package e is a go/doc test for embedded methods. -PACKAGE e - -IMPORTPATH - testdata/e - -FILENAMES - testdata/e.go - -TYPES - // T1 has no embedded (level 1) M method due to conflict. - type T1 struct { - t1 - t2 - } - - // T2 has only M as top-level method. - type T2 struct { - t1 - } - - // T2.M should appear as method of T2. - func (T2) M() - - // T3 has only M as top-level method. - type T3 struct { - t1e - t2e - } - - // T3.M should appear as method of T3. - func (T3) M() - - // - type T4 struct{} - - // T4.M should appear as method of T5 only if AllMethods is set. - func (*T4) M() - - // - type T5 struct { - T4 - } - - // - type U1 struct { - *U1 - } - - // U1.M should appear as method of U1. - func (*U1) M() - - // - type U2 struct { - *U3 - } - - // U2.M should appear as method of U2 and as method of U3 only if ... - func (*U2) M() - - // - type U3 struct { - *U2 - } - - // U3.N should appear as method of U3 and as method of U2 only if ... - func (*U3) N() - - // - type U4 struct { - *u5 - } - - // U4.M should appear as method of U4. - func (*U4) M() - - // - type V1 struct { - *V2 - *V5 - } - - // - type V2 struct { - *V3 - } - - // - type V3 struct { - *V4 - } - - // - type V4 struct { - *V5 - } - - // V4.M should appear as method of V2 and V3 if AllMethods is set. - func (*V4) M() - - // - type V5 struct { - *V6 - } - - // - type V6 struct{} - - // V6.M should appear as method of V1 and V5 if AllMethods is set. - func (*V6) M() - - // - type t1 struct{} - - // t1.M should not appear as method in a Tx type. - func (t1) M() - - // - type t1e struct { - t1 - } - - // t1.M should not appear as method in a Tx type. - func (t1e) M() - - // - type t2 struct{} - - // t2.M should not appear as method in a Tx type. - func (t2) M() - - // - type t2e struct { - t2 - } - - // t2.M should not appear as method in a Tx type. - func (t2e) M() - - // - type u5 struct { - *U4 - } - diff --git a/internal/backport/go/doc/testdata/e.2.golden b/internal/backport/go/doc/testdata/e.2.golden deleted file mode 100644 index e7b05e80fa..0000000000 --- a/internal/backport/go/doc/testdata/e.2.golden +++ /dev/null @@ -1,130 +0,0 @@ -// The package e is a go/doc test for embedded methods. -PACKAGE e - -IMPORTPATH - testdata/e - -FILENAMES - testdata/e.go - -TYPES - // T1 has no embedded (level 1) M method due to conflict. - type T1 struct { - // contains filtered or unexported fields - } - - // T2 has only M as top-level method. - type T2 struct { - // contains filtered or unexported fields - } - - // T2.M should appear as method of T2. - func (T2) M() - - // T3 has only M as top-level method. - type T3 struct { - // contains filtered or unexported fields - } - - // T3.M should appear as method of T3. - func (T3) M() - - // - type T4 struct{} - - // T4.M should appear as method of T5 only if AllMethods is set. - func (*T4) M() - - // - type T5 struct { - T4 - } - - // T4.M should appear as method of T5 only if AllMethods is set. - func (*T5) M() - - // - type U1 struct { - *U1 - } - - // U1.M should appear as method of U1. - func (*U1) M() - - // - type U2 struct { - *U3 - } - - // U2.M should appear as method of U2 and as method of U3 only if ... - func (*U2) M() - - // U3.N should appear as method of U3 and as method of U2 only if ... - func (U2) N() - - // - type U3 struct { - *U2 - } - - // U2.M should appear as method of U2 and as method of U3 only if ... - func (U3) M() - - // U3.N should appear as method of U3 and as method of U2 only if ... - func (*U3) N() - - // - type U4 struct { - // contains filtered or unexported fields - } - - // U4.M should appear as method of U4. - func (*U4) M() - - // - type V1 struct { - *V2 - *V5 - } - - // V6.M should appear as method of V1 and V5 if AllMethods is set. - func (V1) M() - - // - type V2 struct { - *V3 - } - - // V4.M should appear as method of V2 and V3 if AllMethods is set. - func (V2) M() - - // - type V3 struct { - *V4 - } - - // V4.M should appear as method of V2 and V3 if AllMethods is set. - func (V3) M() - - // - type V4 struct { - *V5 - } - - // V4.M should appear as method of V2 and V3 if AllMethods is set. - func (*V4) M() - - // - type V5 struct { - *V6 - } - - // V6.M should appear as method of V1 and V5 if AllMethods is set. - func (V5) M() - - // - type V6 struct{} - - // V6.M should appear as method of V1 and V5 if AllMethods is set. - func (*V6) M() - diff --git a/internal/backport/go/doc/testdata/e.go b/internal/backport/go/doc/testdata/e.go deleted file mode 100644 index ec432e3e52..0000000000 --- a/internal/backport/go/doc/testdata/e.go +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright 2012 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. - -// The package e is a go/doc test for embedded methods. -package e - -// ---------------------------------------------------------------------------- -// Conflicting methods M must not show up. - -type t1 struct{} - -// t1.M should not appear as method in a Tx type. -func (t1) M() {} - -type t2 struct{} - -// t2.M should not appear as method in a Tx type. -func (t2) M() {} - -// T1 has no embedded (level 1) M method due to conflict. -type T1 struct { - t1 - t2 -} - -// ---------------------------------------------------------------------------- -// Higher-level method M wins over lower-level method M. - -// T2 has only M as top-level method. -type T2 struct { - t1 -} - -// T2.M should appear as method of T2. -func (T2) M() {} - -// ---------------------------------------------------------------------------- -// Higher-level method M wins over lower-level conflicting methods M. - -type t1e struct { - t1 -} - -type t2e struct { - t2 -} - -// T3 has only M as top-level method. -type T3 struct { - t1e - t2e -} - -// T3.M should appear as method of T3. -func (T3) M() {} - -// ---------------------------------------------------------------------------- -// Don't show conflicting methods M embedded via an exported and non-exported -// type. - -// T1 has no embedded (level 1) M method due to conflict. -type T4 struct { - t2 - T2 -} - -// ---------------------------------------------------------------------------- -// Don't show embedded methods of exported anonymous fields unless AllMethods -// is set. - -type T4 struct{} - -// T4.M should appear as method of T5 only if AllMethods is set. -func (*T4) M() {} - -type T5 struct { - T4 -} - -// ---------------------------------------------------------------------------- -// Recursive type declarations must not lead to endless recursion. - -type U1 struct { - *U1 -} - -// U1.M should appear as method of U1. -func (*U1) M() {} - -type U2 struct { - *U3 -} - -// U2.M should appear as method of U2 and as method of U3 only if AllMethods is set. -func (*U2) M() {} - -type U3 struct { - *U2 -} - -// U3.N should appear as method of U3 and as method of U2 only if AllMethods is set. -func (*U3) N() {} - -type U4 struct { - *u5 -} - -// U4.M should appear as method of U4. -func (*U4) M() {} - -type u5 struct { - *U4 -} - -// ---------------------------------------------------------------------------- -// A higher-level embedded type (and its methods) wins over the same type (and -// its methods) embedded at a lower level. - -type V1 struct { - *V2 - *V5 -} - -type V2 struct { - *V3 -} - -type V3 struct { - *V4 -} - -type V4 struct { - *V5 -} - -type V5 struct { - *V6 -} - -type V6 struct{} - -// V4.M should appear as method of V2 and V3 if AllMethods is set. -func (*V4) M() {} - -// V6.M should appear as method of V1 and V5 if AllMethods is set. -func (*V6) M() {} diff --git a/internal/backport/go/doc/testdata/error1.0.golden b/internal/backport/go/doc/testdata/error1.0.golden deleted file mode 100644 index 6c6fe5d49b..0000000000 --- a/internal/backport/go/doc/testdata/error1.0.golden +++ /dev/null @@ -1,30 +0,0 @@ -// -PACKAGE error1 - -IMPORTPATH - testdata/error1 - -FILENAMES - testdata/error1.go - -TYPES - // - type I0 interface { - // When embedded, the predeclared error interface - // must remain visible in interface types. - error - } - - // - type S0 struct { - // contains filtered or unexported fields - } - - // - type T0 struct { - ExportedField interface { - // error should be visible - error - } - } - diff --git a/internal/backport/go/doc/testdata/error1.1.golden b/internal/backport/go/doc/testdata/error1.1.golden deleted file mode 100644 index a8dc2e71dc..0000000000 --- a/internal/backport/go/doc/testdata/error1.1.golden +++ /dev/null @@ -1,32 +0,0 @@ -// -PACKAGE error1 - -IMPORTPATH - testdata/error1 - -FILENAMES - testdata/error1.go - -TYPES - // - type I0 interface { - // When embedded, the predeclared error interface - // must remain visible in interface types. - error - } - - // - type S0 struct { - // In struct types, an embedded error must only be visible - // if AllDecls is set. - error - } - - // - type T0 struct { - ExportedField interface { - // error should be visible - error - } - } - diff --git a/internal/backport/go/doc/testdata/error1.2.golden b/internal/backport/go/doc/testdata/error1.2.golden deleted file mode 100644 index 6c6fe5d49b..0000000000 --- a/internal/backport/go/doc/testdata/error1.2.golden +++ /dev/null @@ -1,30 +0,0 @@ -// -PACKAGE error1 - -IMPORTPATH - testdata/error1 - -FILENAMES - testdata/error1.go - -TYPES - // - type I0 interface { - // When embedded, the predeclared error interface - // must remain visible in interface types. - error - } - - // - type S0 struct { - // contains filtered or unexported fields - } - - // - type T0 struct { - ExportedField interface { - // error should be visible - error - } - } - diff --git a/internal/backport/go/doc/testdata/error1.go b/internal/backport/go/doc/testdata/error1.go deleted file mode 100644 index 3c777a7800..0000000000 --- a/internal/backport/go/doc/testdata/error1.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2012 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 error1 - -type I0 interface { - // When embedded, the predeclared error interface - // must remain visible in interface types. - error -} - -type T0 struct { - ExportedField interface { - // error should be visible - error - } -} - -type S0 struct { - // In struct types, an embedded error must only be visible - // if AllDecls is set. - error -} diff --git a/internal/backport/go/doc/testdata/error2.0.golden b/internal/backport/go/doc/testdata/error2.0.golden deleted file mode 100644 index dedfe412a0..0000000000 --- a/internal/backport/go/doc/testdata/error2.0.golden +++ /dev/null @@ -1,27 +0,0 @@ -// -PACKAGE error2 - -IMPORTPATH - testdata/error2 - -FILENAMES - testdata/error2.go - -TYPES - // - type I0 interface { - // contains filtered or unexported methods - } - - // - type S0 struct { - // contains filtered or unexported fields - } - - // - type T0 struct { - ExportedField interface { - // contains filtered or unexported methods - } - } - diff --git a/internal/backport/go/doc/testdata/error2.1.golden b/internal/backport/go/doc/testdata/error2.1.golden deleted file mode 100644 index dbcc1b03e7..0000000000 --- a/internal/backport/go/doc/testdata/error2.1.golden +++ /dev/null @@ -1,37 +0,0 @@ -// -PACKAGE error2 - -IMPORTPATH - testdata/error2 - -FILENAMES - testdata/error2.go - -TYPES - // - type I0 interface { - // When embedded, the locally-declared error interface - // is only visible if all declarations are shown. - error - } - - // - type S0 struct { - // In struct types, an embedded error must only be visible - // if AllDecls is set. - error - } - - // - type T0 struct { - ExportedField interface { - // error should not be visible - error - } - } - - // This error declaration shadows the predeclared error type. - type error interface { - Error() string - } - diff --git a/internal/backport/go/doc/testdata/error2.2.golden b/internal/backport/go/doc/testdata/error2.2.golden deleted file mode 100644 index dedfe412a0..0000000000 --- a/internal/backport/go/doc/testdata/error2.2.golden +++ /dev/null @@ -1,27 +0,0 @@ -// -PACKAGE error2 - -IMPORTPATH - testdata/error2 - -FILENAMES - testdata/error2.go - -TYPES - // - type I0 interface { - // contains filtered or unexported methods - } - - // - type S0 struct { - // contains filtered or unexported fields - } - - // - type T0 struct { - ExportedField interface { - // contains filtered or unexported methods - } - } - diff --git a/internal/backport/go/doc/testdata/error2.go b/internal/backport/go/doc/testdata/error2.go deleted file mode 100644 index 6ee96c2450..0000000000 --- a/internal/backport/go/doc/testdata/error2.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2012 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 error2 - -type I0 interface { - // When embedded, the locally-declared error interface - // is only visible if all declarations are shown. - error -} - -type T0 struct { - ExportedField interface { - // error should not be visible - error - } -} - -type S0 struct { - // In struct types, an embedded error must only be visible - // if AllDecls is set. - error -} - -// This error declaration shadows the predeclared error type. -type error interface { - Error() string -} diff --git a/internal/backport/go/doc/testdata/example.go b/internal/backport/go/doc/testdata/example.go deleted file mode 100644 index fdeda137e7..0000000000 --- a/internal/backport/go/doc/testdata/example.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2009 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 testing - -import ( - "bytes" - "fmt" - "io" - "os" - "strings" - "time" -) - -type InternalExample struct { - Name string - F func() - Output string -} - -func RunExamples(examples []InternalExample) (ok bool) { - ok = true - - var eg InternalExample - - stdout, stderr := os.Stdout, os.Stderr - defer func() { - os.Stdout, os.Stderr = stdout, stderr - if e := recover(); e != nil { - fmt.Printf("--- FAIL: %s\npanic: %v\n", eg.Name, e) - os.Exit(1) - } - }() - - for _, eg = range examples { - if *chatty { - fmt.Printf("=== RUN: %s\n", eg.Name) - } - - // capture stdout and stderr - r, w, err := os.Pipe() - if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - os.Stdout, os.Stderr = w, w - outC := make(chan string) - go func() { - buf := new(bytes.Buffer) - _, err := io.Copy(buf, r) - if err != nil { - fmt.Fprintf(stderr, "testing: copying pipe: %v\n", err) - os.Exit(1) - } - outC <- buf.String() - }() - - // run example - t0 := time.Now() - eg.F() - dt := time.Now().Sub(t0) - - // close pipe, restore stdout/stderr, get output - w.Close() - os.Stdout, os.Stderr = stdout, stderr - out := <-outC - - // report any errors - tstr := fmt.Sprintf("(%.2f seconds)", dt.Seconds()) - if g, e := strings.TrimSpace(out), strings.TrimSpace(eg.Output); g != e { - fmt.Printf("--- FAIL: %s %s\ngot:\n%s\nwant:\n%s\n", - eg.Name, tstr, g, e) - ok = false - } else if *chatty { - fmt.Printf("--- PASS: %s %s\n", eg.Name, tstr) - } - } - - return -} diff --git a/internal/backport/go/doc/testdata/examples/README.md b/internal/backport/go/doc/testdata/examples/README.md deleted file mode 100644 index a1c18e88ec..0000000000 --- a/internal/backport/go/doc/testdata/examples/README.md +++ /dev/null @@ -1,12 +0,0 @@ -These files are processed by example_test.go:TestExamples. - -A .golden file is a txtar file with two sections for each example that should be -created by doc.Examples from the corresponding .go file. - -One section, named EXAMPLE_NAME.Output, contains the example's output, -the value of the field Example.Output. - -The other, named EXAMPLE_NAME.Play, contains the formatted code for a playable -version of the example, the value of the field Example.Play. - -If a section is missing, it is treated as being empty. diff --git a/internal/backport/go/doc/testdata/examples/empty.go b/internal/backport/go/doc/testdata/examples/empty.go deleted file mode 100644 index 0b10420f42..0000000000 --- a/internal/backport/go/doc/testdata/examples/empty.go +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2021 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 p - -func Example() {} -func Example_a() diff --git a/internal/backport/go/doc/testdata/examples/empty.golden b/internal/backport/go/doc/testdata/examples/empty.golden deleted file mode 100644 index 2aafd20435..0000000000 --- a/internal/backport/go/doc/testdata/examples/empty.golden +++ /dev/null @@ -1,6 +0,0 @@ --- .Play -- -package main - -func main() {} -func main() - diff --git a/internal/backport/go/doc/testdata/examples/generic_constraints.go b/internal/backport/go/doc/testdata/examples/generic_constraints.go deleted file mode 100644 index ea5d2b37f4..0000000000 --- a/internal/backport/go/doc/testdata/examples/generic_constraints.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2021 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 p_test - -import ( - "fmt" - "time" -) - -type C1 interface { - string | int -} - -type C2 interface { - M(time.Time) -} - -type G[T C1] int - -func g[T C2](x T) {} - -type Tm int - -func (Tm) M(time.Time) {} - -type Foo int - -func Example() { - fmt.Println("hello") -} - -func ExampleGeneric() { - var x G[string] - g(Tm(3)) - fmt.Println(x) -} diff --git a/internal/backport/go/doc/testdata/examples/generic_constraints.golden b/internal/backport/go/doc/testdata/examples/generic_constraints.golden deleted file mode 100644 index 6c7b0eda9f..0000000000 --- a/internal/backport/go/doc/testdata/examples/generic_constraints.golden +++ /dev/null @@ -1,39 +0,0 @@ --- .Play -- -package main - -import ( - "fmt" -) - -func main() { - fmt.Println("hello") -} --- Generic.Play -- -package main - -import ( - "fmt" - "time" -) - -type C1 interface { - string | int -} - -type C2 interface { - M(time.Time) -} - -type G[T C1] int - -func g[T C2](x T) {} - -type Tm int - -func (Tm) M(time.Time) {} - -func main() { - var x G[string] - g(Tm(3)) - fmt.Println(x) -} diff --git a/internal/backport/go/doc/testdata/examples/import_groups.go b/internal/backport/go/doc/testdata/examples/import_groups.go deleted file mode 100644 index 05f21caaaa..0000000000 --- a/internal/backport/go/doc/testdata/examples/import_groups.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2022 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 foo_test - -import ( - "fmt" - "time" - - "golang.org/x/time/rate" -) - -func Example() { - fmt.Println("Hello, world!") - // Output: Hello, world! -} - -func ExampleLimiter() { - // Uses fmt, time and rate. - l := rate.NewLimiter(rate.Every(time.Second), 1) - fmt.Println(l) -} diff --git a/internal/backport/go/doc/testdata/examples/import_groups.golden b/internal/backport/go/doc/testdata/examples/import_groups.golden deleted file mode 100644 index efe2cc1df5..0000000000 --- a/internal/backport/go/doc/testdata/examples/import_groups.golden +++ /dev/null @@ -1,27 +0,0 @@ --- .Play -- -package main - -import ( - "fmt" -) - -func main() { - fmt.Println("Hello, world!") -} --- .Output -- -Hello, world! --- Limiter.Play -- -package main - -import ( - "fmt" - "time" - - "golang.org/x/time/rate" -) - -func main() { - // Uses fmt, time and rate. - l := rate.NewLimiter(rate.Every(time.Second), 1) - fmt.Println(l) -} diff --git a/internal/backport/go/doc/testdata/examples/import_groups_named.go b/internal/backport/go/doc/testdata/examples/import_groups_named.go deleted file mode 100644 index 377022b22a..0000000000 --- a/internal/backport/go/doc/testdata/examples/import_groups_named.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2022 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 foo_test - -import ( - "fmt" - tm "time" - - r "golang.org/x/time/rate" -) - -func Example() { - fmt.Println("Hello, world!") - // Output: Hello, world! -} - -func ExampleLimiter() { - // Uses fmt, time and rate. - l := r.NewLimiter(r.Every(tm.Second), 1) - fmt.Println(l) -} diff --git a/internal/backport/go/doc/testdata/examples/import_groups_named.golden b/internal/backport/go/doc/testdata/examples/import_groups_named.golden deleted file mode 100644 index 9baf373cd8..0000000000 --- a/internal/backport/go/doc/testdata/examples/import_groups_named.golden +++ /dev/null @@ -1,27 +0,0 @@ --- .Play -- -package main - -import ( - "fmt" -) - -func main() { - fmt.Println("Hello, world!") -} --- .Output -- -Hello, world! --- Limiter.Play -- -package main - -import ( - "fmt" - tm "time" - - r "golang.org/x/time/rate" -) - -func main() { - // Uses fmt, time and rate. - l := r.NewLimiter(r.Every(tm.Second), 1) - fmt.Println(l) -} diff --git a/internal/backport/go/doc/testdata/examples/inspect_signature.go b/internal/backport/go/doc/testdata/examples/inspect_signature.go deleted file mode 100644 index c4a36e758d..0000000000 --- a/internal/backport/go/doc/testdata/examples/inspect_signature.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2021 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 foo_test - -import ( - "bytes" - "io" -) - -func getReader() io.Reader { return nil } - -func do(b bytes.Reader) {} - -func Example() { - getReader() - do() - // Output: -} - -func ExampleIgnored() { -} diff --git a/internal/backport/go/doc/testdata/examples/inspect_signature.golden b/internal/backport/go/doc/testdata/examples/inspect_signature.golden deleted file mode 100644 index c0d9b2ecc8..0000000000 --- a/internal/backport/go/doc/testdata/examples/inspect_signature.golden +++ /dev/null @@ -1,24 +0,0 @@ --- .Play -- -package main - -import ( - "bytes" - "io" -) - -func getReader() io.Reader { return nil } - -func do(b bytes.Reader) {} - -func main() { - getReader() - do() -} --- Ignored.Play -- -package main - -import () - -func main() { -} - diff --git a/internal/backport/go/doc/testdata/examples/iota.go b/internal/backport/go/doc/testdata/examples/iota.go deleted file mode 100644 index c878b7708e..0000000000 --- a/internal/backport/go/doc/testdata/examples/iota.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2021 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 foo_test - -const ( - a = iota - b -) - -const ( - c = 3 - d = 4 -) - -const ( - e = iota - f -) - -// The example refers to only one of the constants in the iota group, but we -// must keep all of them because of the iota. The second group of constants can -// be trimmed. The third has an iota, but is unused, so it can be eliminated. - -func Example() { - _ = b - _ = d -} - -// Need two examples to hit the playExample function. - -func Example2() { -} diff --git a/internal/backport/go/doc/testdata/examples/iota.golden b/internal/backport/go/doc/testdata/examples/iota.golden deleted file mode 100644 index 748770260b..0000000000 --- a/internal/backport/go/doc/testdata/examples/iota.golden +++ /dev/null @@ -1,23 +0,0 @@ --- .Play -- -package main - -import () - -const ( - a = iota - b -) - -const d = 4 - -func main() { - _ = b - _ = d -} --- 2.Play -- -package main - -import () - -func main() { -} diff --git a/internal/backport/go/doc/testdata/examples/issue43658.go b/internal/backport/go/doc/testdata/examples/issue43658.go deleted file mode 100644 index 385223acf3..0000000000 --- a/internal/backport/go/doc/testdata/examples/issue43658.go +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright ©2016 The Gonum Authors. All rights reserved. -// Copyright 2021 The Go Authors. All rights reserved. -// (above line required for our license-header checker) -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package community_test - -import ( - "fmt" - "log" - "sort" - - "golang.org/x/exp/rand" - - "gonum.org/v1/gonum/graph/community" - "gonum.org/v1/gonum/graph/internal/ordered" - "gonum.org/v1/gonum/graph/simple" -) - -func ExampleProfile_simple() { - // Profile calls Modularize which implements the Louvain modularization algorithm. - // Since this is a randomized algorithm we use a defined random source to ensure - // consistency between test runs. In practice, results will not differ greatly - // between runs with different PRNG seeds. - src := rand.NewSource(1) - - // Create dumbell graph: - // - // 0 4 - // |\ /| - // | 2 - 3 | - // |/ \| - // 1 5 - // - g := simple.NewUndirectedGraph() - for u, e := range smallDumbell { - for v := range e { - g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) - } - } - - // Get the profile of internal node weight for resolutions - // between 0.1 and 10 using logarithmic bisection. - p, err := community.Profile( - community.ModularScore(g, community.Weight, 10, src), - true, 1e-3, 0.1, 10, - ) - if err != nil { - log.Fatal(err) - } - - // Print out each step with communities ordered. - for _, d := range p { - comm := d.Communities() - for _, c := range comm { - sort.Sort(ordered.ByID(c)) - } - sort.Sort(ordered.BySliceIDs(comm)) - fmt.Printf("Low:%.2v High:%.2v Score:%v Communities:%v Q=%.3v\n", - d.Low, d.High, d.Score, comm, community.Q(g, comm, d.Low)) - } - - // Output: - // Low:0.1 High:0.29 Score:14 Communities:[[0 1 2 3 4 5]] Q=0.9 - // Low:0.29 High:2.3 Score:12 Communities:[[0 1 2] [3 4 5]] Q=0.714 - // Low:2.3 High:3.5 Score:4 Communities:[[0 1] [2] [3] [4 5]] Q=-0.31 - // Low:3.5 High:10 Score:0 Communities:[[0] [1] [2] [3] [4] [5]] Q=-0.607 -} - -// intset is an integer set. -type intset map[int]struct{} - -func linksTo(i ...int) intset { - if len(i) == 0 { - return nil - } - s := make(intset) - for _, v := range i { - s[v] = struct{}{} - } - return s -} - -var ( - smallDumbell = []intset{ - 0: linksTo(1, 2), - 1: linksTo(2), - 2: linksTo(3), - 3: linksTo(4, 5), - 4: linksTo(5), - 5: nil, - } - - // http://www.slate.com/blogs/the_world_/2014/07/17/the_middle_east_friendship_chart.html - middleEast = struct{ friends, complicated, enemies []intset }{ - // green cells - friends: []intset{ - 0: nil, - 1: linksTo(5, 7, 9, 12), - 2: linksTo(11), - 3: linksTo(4, 5, 10), - 4: linksTo(3, 5, 10), - 5: linksTo(1, 3, 4, 8, 10, 12), - 6: nil, - 7: linksTo(1, 12), - 8: linksTo(5, 9, 11), - 9: linksTo(1, 8, 12), - 10: linksTo(3, 4, 5), - 11: linksTo(2, 8), - 12: linksTo(1, 5, 7, 9), - }, - - // yellow cells - complicated: []intset{ - 0: linksTo(2, 4), - 1: linksTo(4, 8), - 2: linksTo(0, 3, 4, 5, 8, 9), - 3: linksTo(2, 8, 11), - 4: linksTo(0, 1, 2, 8), - 5: linksTo(2), - 6: nil, - 7: linksTo(9, 11), - 8: linksTo(1, 2, 3, 4, 10, 12), - 9: linksTo(2, 7, 11), - 10: linksTo(8), - 11: linksTo(3, 7, 9, 12), - 12: linksTo(8, 11), - }, - - // red cells - enemies: []intset{ - 0: linksTo(1, 3, 5, 6, 7, 8, 9, 10, 11, 12), - 1: linksTo(0, 2, 3, 6, 10, 11), - 2: linksTo(1, 6, 7, 10, 12), - 3: linksTo(0, 1, 6, 7, 9, 12), - 4: linksTo(6, 7, 9, 11, 12), - 5: linksTo(0, 6, 7, 9, 11), - 6: linksTo(0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12), - 7: linksTo(0, 2, 3, 4, 5, 6, 8, 10), - 8: linksTo(0, 6, 7), - 9: linksTo(0, 3, 4, 5, 6, 10), - 10: linksTo(0, 1, 2, 6, 7, 9, 11, 12), - 11: linksTo(0, 1, 4, 5, 6, 10), - 12: linksTo(0, 2, 3, 4, 6, 10), - }, - } -) - -var friends, enemies *simple.WeightedUndirectedGraph - -func init() { - friends = simple.NewWeightedUndirectedGraph(0, 0) - for u, e := range middleEast.friends { - // Ensure unconnected nodes are included. - if friends.Node(int64(u)) == nil { - friends.AddNode(simple.Node(u)) - } - for v := range e { - friends.SetWeightedEdge(simple.WeightedEdge{F: simple.Node(u), T: simple.Node(v), W: 1}) - } - } - enemies = simple.NewWeightedUndirectedGraph(0, 0) - for u, e := range middleEast.enemies { - // Ensure unconnected nodes are included. - if enemies.Node(int64(u)) == nil { - enemies.AddNode(simple.Node(u)) - } - for v := range e { - enemies.SetWeightedEdge(simple.WeightedEdge{F: simple.Node(u), T: simple.Node(v), W: -1}) - } - } -} - -func ExampleProfile_multiplex() { - // Profile calls ModularizeMultiplex which implements the Louvain modularization - // algorithm. Since this is a randomized algorithm we use a defined random source - // to ensure consistency between test runs. In practice, results will not differ - // greatly between runs with different PRNG seeds. - src := rand.NewSource(1) - - // The undirected graphs, friends and enemies, are the political relationships - // in the Middle East as described in the Slate article: - // http://www.slate.com/blogs/the_world_/2014/07/17/the_middle_east_friendship_chart.html - g, err := community.NewUndirectedLayers(friends, enemies) - if err != nil { - log.Fatal(err) - } - weights := []float64{1, -1} - - // Get the profile of internal node weight for resolutions - // between 0.1 and 10 using logarithmic bisection. - p, err := community.Profile( - community.ModularMultiplexScore(g, weights, true, community.WeightMultiplex, 10, src), - true, 1e-3, 0.1, 10, - ) - if err != nil { - log.Fatal(err) - } - - // Print out each step with communities ordered. - for _, d := range p { - comm := d.Communities() - for _, c := range comm { - sort.Sort(ordered.ByID(c)) - } - sort.Sort(ordered.BySliceIDs(comm)) - fmt.Printf("Low:%.2v High:%.2v Score:%v Communities:%v Q=%.3v\n", - d.Low, d.High, d.Score, comm, community.QMultiplex(g, comm, weights, []float64{d.Low})) - } - - // Output: - // Low:0.1 High:0.72 Score:26 Communities:[[0] [1 7 9 12] [2 8 11] [3 4 5 10] [6]] Q=[24.7 1.97] - // Low:0.72 High:1.1 Score:24 Communities:[[0 6] [1 7 9 12] [2 8 11] [3 4 5 10]] Q=[16.9 14.1] - // Low:1.1 High:1.2 Score:18 Communities:[[0 2 6 11] [1 7 9 12] [3 4 5 8 10]] Q=[9.16 25.1] - // Low:1.2 High:1.6 Score:10 Communities:[[0 3 4 5 6 10] [1 7 9 12] [2 8 11]] Q=[10.5 26.7] - // Low:1.6 High:1.6 Score:8 Communities:[[0 1 6 7 9 12] [2 8 11] [3 4 5 10]] Q=[5.56 39.8] - // Low:1.6 High:1.8 Score:2 Communities:[[0 2 3 4 5 6 10] [1 7 8 9 11 12]] Q=[-1.82 48.6] - // Low:1.8 High:2.3 Score:-6 Communities:[[0 2 3 4 5 6 8 10 11] [1 7 9 12]] Q=[-5 57.5] - // Low:2.3 High:2.4 Score:-10 Communities:[[0 1 2 6 7 8 9 11 12] [3 4 5 10]] Q=[-11.2 79] - // Low:2.4 High:4.3 Score:-52 Communities:[[0 1 2 3 4 5 6 7 8 9 10 11 12]] Q=[-46.1 117] - // Low:4.3 High:10 Score:-54 Communities:[[0 1 2 3 4 6 7 8 9 10 11 12] [5]] Q=[-82 254] -} diff --git a/internal/backport/go/doc/testdata/examples/issue43658.golden b/internal/backport/go/doc/testdata/examples/issue43658.golden deleted file mode 100644 index 5200d14584..0000000000 --- a/internal/backport/go/doc/testdata/examples/issue43658.golden +++ /dev/null @@ -1,156 +0,0 @@ --- Profile_simple.Play -- -package main - -import ( - "fmt" - "log" - "sort" - - "golang.org/x/exp/rand" - - "gonum.org/v1/gonum/graph/community" - "gonum.org/v1/gonum/graph/internal/ordered" - "gonum.org/v1/gonum/graph/simple" -) - -func main() { - // Profile calls Modularize which implements the Louvain modularization algorithm. - // Since this is a randomized algorithm we use a defined random source to ensure - // consistency between test runs. In practice, results will not differ greatly - // between runs with different PRNG seeds. - src := rand.NewSource(1) - - // Create dumbell graph: - // - // 0 4 - // |\ /| - // | 2 - 3 | - // |/ \| - // 1 5 - // - g := simple.NewUndirectedGraph() - for u, e := range smallDumbell { - for v := range e { - g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) - } - } - - // Get the profile of internal node weight for resolutions - // between 0.1 and 10 using logarithmic bisection. - p, err := community.Profile( - community.ModularScore(g, community.Weight, 10, src), - true, 1e-3, 0.1, 10, - ) - if err != nil { - log.Fatal(err) - } - - // Print out each step with communities ordered. - for _, d := range p { - comm := d.Communities() - for _, c := range comm { - sort.Sort(ordered.ByID(c)) - } - sort.Sort(ordered.BySliceIDs(comm)) - fmt.Printf("Low:%.2v High:%.2v Score:%v Communities:%v Q=%.3v\n", - d.Low, d.High, d.Score, comm, community.Q(g, comm, d.Low)) - } - -} - -// intset is an integer set. -type intset map[int]struct{} - -func linksTo(i ...int) intset { - if len(i) == 0 { - return nil - } - s := make(intset) - for _, v := range i { - s[v] = struct{}{} - } - return s -} - -var smallDumbell = []intset{ - 0: linksTo(1, 2), - 1: linksTo(2), - 2: linksTo(3), - 3: linksTo(4, 5), - 4: linksTo(5), - 5: nil, -} - --- Profile_simple.Output -- -Low:0.1 High:0.29 Score:14 Communities:[[0 1 2 3 4 5]] Q=0.9 -Low:0.29 High:2.3 Score:12 Communities:[[0 1 2] [3 4 5]] Q=0.714 -Low:2.3 High:3.5 Score:4 Communities:[[0 1] [2] [3] [4 5]] Q=-0.31 -Low:3.5 High:10 Score:0 Communities:[[0] [1] [2] [3] [4] [5]] Q=-0.607 - --- Profile_multiplex.Play -- - -package main - -import ( - "fmt" - "log" - "sort" - - "golang.org/x/exp/rand" - - "gonum.org/v1/gonum/graph/community" - "gonum.org/v1/gonum/graph/internal/ordered" - "gonum.org/v1/gonum/graph/simple" -) - -var friends, enemies *simple.WeightedUndirectedGraph - -func main() { - // Profile calls ModularizeMultiplex which implements the Louvain modularization - // algorithm. Since this is a randomized algorithm we use a defined random source - // to ensure consistency between test runs. In practice, results will not differ - // greatly between runs with different PRNG seeds. - src := rand.NewSource(1) - - // The undirected graphs, friends and enemies, are the political relationships - // in the Middle East as described in the Slate article: - // http://www.slate.com/blogs/the_world_/2014/07/17/the_middle_east_friendship_chart.html - g, err := community.NewUndirectedLayers(friends, enemies) - if err != nil { - log.Fatal(err) - } - weights := []float64{1, -1} - - // Get the profile of internal node weight for resolutions - // between 0.1 and 10 using logarithmic bisection. - p, err := community.Profile( - community.ModularMultiplexScore(g, weights, true, community.WeightMultiplex, 10, src), - true, 1e-3, 0.1, 10, - ) - if err != nil { - log.Fatal(err) - } - - // Print out each step with communities ordered. - for _, d := range p { - comm := d.Communities() - for _, c := range comm { - sort.Sort(ordered.ByID(c)) - } - sort.Sort(ordered.BySliceIDs(comm)) - fmt.Printf("Low:%.2v High:%.2v Score:%v Communities:%v Q=%.3v\n", - d.Low, d.High, d.Score, comm, community.QMultiplex(g, comm, weights, []float64{d.Low})) - } - -} --- Profile_multiplex.Output -- -Low:0.1 High:0.72 Score:26 Communities:[[0] [1 7 9 12] [2 8 11] [3 4 5 10] [6]] Q=[24.7 1.97] -Low:0.72 High:1.1 Score:24 Communities:[[0 6] [1 7 9 12] [2 8 11] [3 4 5 10]] Q=[16.9 14.1] -Low:1.1 High:1.2 Score:18 Communities:[[0 2 6 11] [1 7 9 12] [3 4 5 8 10]] Q=[9.16 25.1] -Low:1.2 High:1.6 Score:10 Communities:[[0 3 4 5 6 10] [1 7 9 12] [2 8 11]] Q=[10.5 26.7] -Low:1.6 High:1.6 Score:8 Communities:[[0 1 6 7 9 12] [2 8 11] [3 4 5 10]] Q=[5.56 39.8] -Low:1.6 High:1.8 Score:2 Communities:[[0 2 3 4 5 6 10] [1 7 8 9 11 12]] Q=[-1.82 48.6] -Low:1.8 High:2.3 Score:-6 Communities:[[0 2 3 4 5 6 8 10 11] [1 7 9 12]] Q=[-5 57.5] -Low:2.3 High:2.4 Score:-10 Communities:[[0 1 2 6 7 8 9 11 12] [3 4 5 10]] Q=[-11.2 79] -Low:2.4 High:4.3 Score:-52 Communities:[[0 1 2 3 4 5 6 7 8 9 10 11 12]] Q=[-46.1 117] -Low:4.3 High:10 Score:-54 Communities:[[0 1 2 3 4 6 7 8 9 10 11 12] [5]] Q=[-82 254] diff --git a/internal/backport/go/doc/testdata/examples/multiple.go b/internal/backport/go/doc/testdata/examples/multiple.go deleted file mode 100644 index 27282644d8..0000000000 --- a/internal/backport/go/doc/testdata/examples/multiple.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2021 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 foo_test - -import ( - "flag" - "fmt" - "log" - "os/exec" - "sort" -) - -func ExampleHello() { - fmt.Println("Hello, world!") - // Output: Hello, world! -} - -func ExampleImport() { - out, err := exec.Command("date").Output() - if err != nil { - log.Fatal(err) - } - fmt.Printf("The date is %s\n", out) -} - -func ExampleKeyValue() { - v := struct { - a string - b int - }{ - a: "A", - b: 1, - } - fmt.Print(v) - // Output: a: "A", b: 1 -} - -func ExampleKeyValueImport() { - f := flag.Flag{ - Name: "play", - } - fmt.Print(f) - // Output: Name: "play" -} - -var keyValueTopDecl = struct { - a string - b int -}{ - a: "B", - b: 2, -} - -func ExampleKeyValueTopDecl() { - fmt.Print(keyValueTopDecl) - // Output: a: "B", b: 2 -} - -// Person represents a person by name and age. -type Person struct { - Name string - Age int -} - -// String returns a string representation of the Person. -func (p Person) String() string { - return fmt.Sprintf("%s: %d", p.Name, p.Age) -} - -// ByAge implements sort.Interface for []Person based on -// the Age field. -type ByAge []Person - -// Len returns the number of elements in ByAge. -func (a ByAge) Len() int { return len(a) } - -// Swap swaps the elements in ByAge. -func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age } - -// people is the array of Person -var people = []Person{ - {"Bob", 31}, - {"John", 42}, - {"Michael", 17}, - {"Jenny", 26}, -} - -func ExampleSort() { - fmt.Println(people) - sort.Sort(ByAge(people)) - fmt.Println(people) - // Output: - // [Bob: 31 John: 42 Michael: 17 Jenny: 26] - // [Michael: 17 Jenny: 26 Bob: 31 John: 42] -} diff --git a/internal/backport/go/doc/testdata/examples/multiple.golden b/internal/backport/go/doc/testdata/examples/multiple.golden deleted file mode 100644 index d2d791effa..0000000000 --- a/internal/backport/go/doc/testdata/examples/multiple.golden +++ /dev/null @@ -1,129 +0,0 @@ --- Hello.Play -- -package main - -import ( - "fmt" -) - -func main() { - fmt.Println("Hello, world!") -} --- Hello.Output -- -Hello, world! --- Import.Play -- -package main - -import ( - "fmt" - "log" - "os/exec" -) - -func main() { - out, err := exec.Command("date").Output() - if err != nil { - log.Fatal(err) - } - fmt.Printf("The date is %s\n", out) -} --- KeyValue.Play -- -package main - -import ( - "fmt" -) - -func main() { - v := struct { - a string - b int - }{ - a: "A", - b: 1, - } - fmt.Print(v) -} --- KeyValue.Output -- -a: "A", b: 1 --- KeyValueImport.Play -- -package main - -import ( - "flag" - "fmt" -) - -func main() { - f := flag.Flag{ - Name: "play", - } - fmt.Print(f) -} --- KeyValueImport.Output -- -Name: "play" --- KeyValueTopDecl.Play -- -package main - -import ( - "fmt" -) - -var keyValueTopDecl = struct { - a string - b int -}{ - a: "B", - b: 2, -} - -func main() { - fmt.Print(keyValueTopDecl) -} --- KeyValueTopDecl.Output -- -a: "B", b: 2 --- Sort.Play -- -package main - -import ( - "fmt" - "sort" -) - -// Person represents a person by name and age. -type Person struct { - Name string - Age int -} - -// String returns a string representation of the Person. -func (p Person) String() string { - return fmt.Sprintf("%s: %d", p.Name, p.Age) -} - -// ByAge implements sort.Interface for []Person based on -// the Age field. -type ByAge []Person - -// Len returns the number of elements in ByAge. -func (a ByAge) Len() int { return len(a) } - -// Swap swaps the elements in ByAge. -func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age } - -// people is the array of Person -var people = []Person{ - {"Bob", 31}, - {"John", 42}, - {"Michael", 17}, - {"Jenny", 26}, -} - -func main() { - fmt.Println(people) - sort.Sort(ByAge(people)) - fmt.Println(people) -} --- Sort.Output -- -[Bob: 31 John: 42 Michael: 17 Jenny: 26] -[Michael: 17 Jenny: 26 Bob: 31 John: 42] diff --git a/internal/backport/go/doc/testdata/examples/values.go b/internal/backport/go/doc/testdata/examples/values.go deleted file mode 100644 index 64b0de476d..0000000000 --- a/internal/backport/go/doc/testdata/examples/values.go +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2021 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 foo_test - -// Variable declaration with fewer values than names. - -func f() (int, int) { - return 1, 2 -} - -var a, b = f() - -// Need two examples to hit playExample. - -func ExampleA() { - _ = a -} - -func ExampleB() { -} diff --git a/internal/backport/go/doc/testdata/examples/values.golden b/internal/backport/go/doc/testdata/examples/values.golden deleted file mode 100644 index 00c19917af..0000000000 --- a/internal/backport/go/doc/testdata/examples/values.golden +++ /dev/null @@ -1,21 +0,0 @@ --- A.Play -- -package main - -import () - -func f() (int, int) { - return 1, 2 -} - -var a, b = f() - -func main() { - _ = a -} --- B.Play -- -package main - -import () - -func main() { -} diff --git a/internal/backport/go/doc/testdata/examples/whole_file.go b/internal/backport/go/doc/testdata/examples/whole_file.go deleted file mode 100644 index 61954cecea..0000000000 --- a/internal/backport/go/doc/testdata/examples/whole_file.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2021 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 foo_test - -import "fmt" - -type X int - -func (X) Foo() { -} - -func (X) TestBlah() { -} - -func (X) BenchmarkFoo() { -} - -func Example() { - fmt.Println("Hello, world!") - // Output: Hello, world! -} diff --git a/internal/backport/go/doc/testdata/examples/whole_file.golden b/internal/backport/go/doc/testdata/examples/whole_file.golden deleted file mode 100644 index 74a2291f51..0000000000 --- a/internal/backport/go/doc/testdata/examples/whole_file.golden +++ /dev/null @@ -1,21 +0,0 @@ --- .Play -- -package main - -import "fmt" - -type X int - -func (X) Foo() { -} - -func (X) TestBlah() { -} - -func (X) BenchmarkFoo() { -} - -func main() { - fmt.Println("Hello, world!") -} --- .Output -- -Hello, world! diff --git a/internal/backport/go/doc/testdata/examples/whole_function.go b/internal/backport/go/doc/testdata/examples/whole_function.go deleted file mode 100644 index 1754ee3412..0000000000 --- a/internal/backport/go/doc/testdata/examples/whole_function.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2021 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 foo_test - -func Foo(x int) { -} - -func Example() { - fmt.Println("Hello, world!") - // Output: Hello, world! -} diff --git a/internal/backport/go/doc/testdata/examples/whole_function.golden b/internal/backport/go/doc/testdata/examples/whole_function.golden deleted file mode 100644 index 7d5b5cbd7f..0000000000 --- a/internal/backport/go/doc/testdata/examples/whole_function.golden +++ /dev/null @@ -1,11 +0,0 @@ --- .Play -- -package main - -func Foo(x int) { -} - -func main() { - fmt.Println("Hello, world!") -} --- .Output -- -Hello, world! diff --git a/internal/backport/go/doc/testdata/examples/whole_function_external.go b/internal/backport/go/doc/testdata/examples/whole_function_external.go deleted file mode 100644 index 0e0e2f5856..0000000000 --- a/internal/backport/go/doc/testdata/examples/whole_function_external.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2021 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 foo_test - -func foo(int) - -func Example() { - foo(42) - // Output: -} diff --git a/internal/backport/go/doc/testdata/examples/whole_function_external.golden b/internal/backport/go/doc/testdata/examples/whole_function_external.golden deleted file mode 100644 index ec8f114abe..0000000000 --- a/internal/backport/go/doc/testdata/examples/whole_function_external.golden +++ /dev/null @@ -1,8 +0,0 @@ --- .Play -- -package main - -func foo(int) - -func main() { - foo(42) -} diff --git a/internal/backport/go/doc/testdata/f.0.golden b/internal/backport/go/doc/testdata/f.0.golden deleted file mode 100644 index 8175901861..0000000000 --- a/internal/backport/go/doc/testdata/f.0.golden +++ /dev/null @@ -1,13 +0,0 @@ -// The package f is a go/doc test for functions and factory ... -PACKAGE f - -IMPORTPATH - testdata/f - -FILENAMES - testdata/f.go - -FUNCTIONS - // Exported must always be visible. Was issue 2824. - func Exported() private - diff --git a/internal/backport/go/doc/testdata/f.1.golden b/internal/backport/go/doc/testdata/f.1.golden deleted file mode 100644 index ba68e884c2..0000000000 --- a/internal/backport/go/doc/testdata/f.1.golden +++ /dev/null @@ -1,16 +0,0 @@ -// The package f is a go/doc test for functions and factory ... -PACKAGE f - -IMPORTPATH - testdata/f - -FILENAMES - testdata/f.go - -TYPES - // - type private struct{} - - // Exported must always be visible. Was issue 2824. - func Exported() private - diff --git a/internal/backport/go/doc/testdata/f.2.golden b/internal/backport/go/doc/testdata/f.2.golden deleted file mode 100644 index 8175901861..0000000000 --- a/internal/backport/go/doc/testdata/f.2.golden +++ /dev/null @@ -1,13 +0,0 @@ -// The package f is a go/doc test for functions and factory ... -PACKAGE f - -IMPORTPATH - testdata/f - -FILENAMES - testdata/f.go - -FUNCTIONS - // Exported must always be visible. Was issue 2824. - func Exported() private - diff --git a/internal/backport/go/doc/testdata/f.go b/internal/backport/go/doc/testdata/f.go deleted file mode 100644 index 7e9add9078..0000000000 --- a/internal/backport/go/doc/testdata/f.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2012 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. - -// The package f is a go/doc test for functions and factory methods. -package f - -// ---------------------------------------------------------------------------- -// Factory functions for non-exported types must not get lost. - -type private struct{} - -// Exported must always be visible. Was issue 2824. -func Exported() private {} diff --git a/internal/backport/go/doc/testdata/g.0.golden b/internal/backport/go/doc/testdata/g.0.golden deleted file mode 100644 index 487cf06ae5..0000000000 --- a/internal/backport/go/doc/testdata/g.0.golden +++ /dev/null @@ -1,32 +0,0 @@ -// The package g is a go/doc test for mixed exported/unexported ... -PACKAGE g - -IMPORTPATH - testdata/g - -FILENAMES - testdata/g.go - -CONSTANTS - // - const ( - A, _ = iota, iota - _, D - E, _ - G, H - ) - - -VARIABLES - // - var ( - _, C2, _ = 1, 2, 3 - C4, _, C6 = 4, 5, 6 - _, C8, _ = 7, 8, 9 - ) - - // - var ( - _, X = f() - ) - diff --git a/internal/backport/go/doc/testdata/g.1.golden b/internal/backport/go/doc/testdata/g.1.golden deleted file mode 100644 index 438441ac54..0000000000 --- a/internal/backport/go/doc/testdata/g.1.golden +++ /dev/null @@ -1,34 +0,0 @@ -// The package g is a go/doc test for mixed exported/unexported ... -PACKAGE g - -IMPORTPATH - testdata/g - -FILENAMES - testdata/g.go - -CONSTANTS - // - const ( - A, b = iota, iota - c, D - E, f - G, H - ) - - -VARIABLES - // - var ( - c1, C2, c3 = 1, 2, 3 - C4, c5, C6 = 4, 5, 6 - c7, C8, c9 = 7, 8, 9 - xx, yy, zz = 0, 0, 0 // all unexported and hidden - ) - - // - var ( - x, X = f() - y, z = f() - ) - diff --git a/internal/backport/go/doc/testdata/g.2.golden b/internal/backport/go/doc/testdata/g.2.golden deleted file mode 100644 index 487cf06ae5..0000000000 --- a/internal/backport/go/doc/testdata/g.2.golden +++ /dev/null @@ -1,32 +0,0 @@ -// The package g is a go/doc test for mixed exported/unexported ... -PACKAGE g - -IMPORTPATH - testdata/g - -FILENAMES - testdata/g.go - -CONSTANTS - // - const ( - A, _ = iota, iota - _, D - E, _ - G, H - ) - - -VARIABLES - // - var ( - _, C2, _ = 1, 2, 3 - C4, _, C6 = 4, 5, 6 - _, C8, _ = 7, 8, 9 - ) - - // - var ( - _, X = f() - ) - diff --git a/internal/backport/go/doc/testdata/g.go b/internal/backport/go/doc/testdata/g.go deleted file mode 100644 index ceeb417443..0000000000 --- a/internal/backport/go/doc/testdata/g.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2018 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. - -// The package g is a go/doc test for mixed exported/unexported values. -package g - -const ( - A, b = iota, iota - c, D - E, f - G, H -) - -var ( - c1, C2, c3 = 1, 2, 3 - C4, c5, C6 = 4, 5, 6 - c7, C8, c9 = 7, 8, 9 - xx, yy, zz = 0, 0, 0 // all unexported and hidden -) - -var ( - x, X = f() - y, z = f() -) diff --git a/internal/backport/go/doc/testdata/generics.0.golden b/internal/backport/go/doc/testdata/generics.0.golden deleted file mode 100644 index 91c874c84d..0000000000 --- a/internal/backport/go/doc/testdata/generics.0.golden +++ /dev/null @@ -1,76 +0,0 @@ -// Package generics contains the new syntax supporting generic ... -PACKAGE generics - -IMPORTPATH - testdata/generics - -FILENAMES - testdata/generics.go - -FUNCTIONS - // AnotherFunc has an implicit constraint interface. Neither type ... - func AnotherFunc[T ~struct{ f int }](_ struct{ f int }) - - // Func has an instantiated constraint. - func Func[T Constraint[string, Type[int]]]() - - // Single is not a factory function. - func Single[T any]() *T - - // Slice is not a factory function. - func Slice[T any]() []T - - -TYPES - // AFuncType demonstrates filtering of parameters and type ... - type AFuncType[T ~struct{ f int }] func(_ struct { - // contains filtered or unexported fields - }) - - // Constraint is a constraint interface with two type parameters. - type Constraint[P, Q interface{ string | ~int | Type[int] }] interface { - ~int | ~byte | Type[string] - M() P - } - - // NewEmbeddings demonstrates how we filter the new embedded ... - type NewEmbeddings interface { - string // should not be filtered - - struct { - // contains filtered or unexported fields - } - ~struct { - // contains filtered or unexported fields - } - *struct { - // contains filtered or unexported fields - } - struct { - // contains filtered or unexported fields - } | ~struct { - // contains filtered or unexported fields - } - // contains filtered or unexported methods - } - - // Parameterized types should be shown. - type Type[P any] struct { - Field P - } - - // Variables with an instantiated type should be shown. - var X Type[int] - - // Constructors for parameterized types should be shown. - func Constructor[lowerCase any]() Type[lowerCase] - - // MethodA uses a different name for its receiver type parameter. - func (t Type[A]) MethodA(p A) - - // MethodB has a blank receiver type parameter. - func (t Type[_]) MethodB() - - // MethodC has a lower-case receiver type parameter. - func (t Type[c]) MethodC() - diff --git a/internal/backport/go/doc/testdata/generics.1.golden b/internal/backport/go/doc/testdata/generics.1.golden deleted file mode 100644 index 923a4ce5d9..0000000000 --- a/internal/backport/go/doc/testdata/generics.1.golden +++ /dev/null @@ -1,66 +0,0 @@ -// Package generics contains the new syntax supporting generic ... -PACKAGE generics - -IMPORTPATH - testdata/generics - -FILENAMES - testdata/generics.go - -FUNCTIONS - // AnotherFunc has an implicit constraint interface. Neither type ... - func AnotherFunc[T ~struct{ f int }](_ struct{ f int }) - - // Func has an instantiated constraint. - func Func[T Constraint[string, Type[int]]]() - - // Single is not a factory function. - func Single[T any]() *T - - // Slice is not a factory function. - func Slice[T any]() []T - - -TYPES - // AFuncType demonstrates filtering of parameters and type ... - type AFuncType[T ~struct{ f int }] func(_ struct{ f int }) - - // Constraint is a constraint interface with two type parameters. - type Constraint[P, Q interface{ string | ~int | Type[int] }] interface { - ~int | ~byte | Type[string] - M() P - } - - // NewEmbeddings demonstrates how we filter the new embedded ... - type NewEmbeddings interface { - string // should not be filtered - int16 - struct{ f int } - ~struct{ f int } - *struct{ f int } - struct{ f int } | ~struct{ f int } - } - - // Parameterized types should be shown. - type Type[P any] struct { - Field P - } - - // Variables with an instantiated type should be shown. - var X Type[int] - - // Constructors for parameterized types should be shown. - func Constructor[lowerCase any]() Type[lowerCase] - - // MethodA uses a different name for its receiver type parameter. - func (t Type[A]) MethodA(p A) - - // MethodB has a blank receiver type parameter. - func (t Type[_]) MethodB() - - // MethodC has a lower-case receiver type parameter. - func (t Type[c]) MethodC() - - // int16 shadows the predeclared type int16. - type int16 int - diff --git a/internal/backport/go/doc/testdata/generics.2.golden b/internal/backport/go/doc/testdata/generics.2.golden deleted file mode 100644 index 91c874c84d..0000000000 --- a/internal/backport/go/doc/testdata/generics.2.golden +++ /dev/null @@ -1,76 +0,0 @@ -// Package generics contains the new syntax supporting generic ... -PACKAGE generics - -IMPORTPATH - testdata/generics - -FILENAMES - testdata/generics.go - -FUNCTIONS - // AnotherFunc has an implicit constraint interface. Neither type ... - func AnotherFunc[T ~struct{ f int }](_ struct{ f int }) - - // Func has an instantiated constraint. - func Func[T Constraint[string, Type[int]]]() - - // Single is not a factory function. - func Single[T any]() *T - - // Slice is not a factory function. - func Slice[T any]() []T - - -TYPES - // AFuncType demonstrates filtering of parameters and type ... - type AFuncType[T ~struct{ f int }] func(_ struct { - // contains filtered or unexported fields - }) - - // Constraint is a constraint interface with two type parameters. - type Constraint[P, Q interface{ string | ~int | Type[int] }] interface { - ~int | ~byte | Type[string] - M() P - } - - // NewEmbeddings demonstrates how we filter the new embedded ... - type NewEmbeddings interface { - string // should not be filtered - - struct { - // contains filtered or unexported fields - } - ~struct { - // contains filtered or unexported fields - } - *struct { - // contains filtered or unexported fields - } - struct { - // contains filtered or unexported fields - } | ~struct { - // contains filtered or unexported fields - } - // contains filtered or unexported methods - } - - // Parameterized types should be shown. - type Type[P any] struct { - Field P - } - - // Variables with an instantiated type should be shown. - var X Type[int] - - // Constructors for parameterized types should be shown. - func Constructor[lowerCase any]() Type[lowerCase] - - // MethodA uses a different name for its receiver type parameter. - func (t Type[A]) MethodA(p A) - - // MethodB has a blank receiver type parameter. - func (t Type[_]) MethodB() - - // MethodC has a lower-case receiver type parameter. - func (t Type[c]) MethodC() - diff --git a/internal/backport/go/doc/testdata/generics.go b/internal/backport/go/doc/testdata/generics.go deleted file mode 100644 index ba7187e4dd..0000000000 --- a/internal/backport/go/doc/testdata/generics.go +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2021 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 generics contains the new syntax supporting generic programming in -// Go. -package generics - -// Variables with an instantiated type should be shown. -var X Type[int] - -// Parameterized types should be shown. -type Type[P any] struct { - Field P -} - -// Constructors for parameterized types should be shown. -func Constructor[lowerCase any]() Type[lowerCase] { - return Type[lowerCase]{} -} - -// MethodA uses a different name for its receiver type parameter. -func (t Type[A]) MethodA(p A) {} - -// MethodB has a blank receiver type parameter. -func (t Type[_]) MethodB() {} - -// MethodC has a lower-case receiver type parameter. -func (t Type[c]) MethodC() {} - -// Constraint is a constraint interface with two type parameters. -type Constraint[P, Q interface{ string | ~int | Type[int] }] interface { - ~int | ~byte | Type[string] - M() P -} - -// int16 shadows the predeclared type int16. -type int16 int - -// NewEmbeddings demonstrates how we filter the new embedded elements. -type NewEmbeddings interface { - string // should not be filtered - int16 - struct{ f int } - ~struct{ f int } - *struct{ f int } - struct{ f int } | ~struct{ f int } -} - -// Func has an instantiated constraint. -func Func[T Constraint[string, Type[int]]]() {} - -// AnotherFunc has an implicit constraint interface. -// -// Neither type parameters nor regular parameters should be filtered. -func AnotherFunc[T ~struct{ f int }](_ struct{ f int }) {} - -// AFuncType demonstrates filtering of parameters and type parameters. Here we -// don't filter type parameters (to be consistent with function declarations), -// but DO filter the RHS. -type AFuncType[T ~struct{ f int }] func(_ struct{ f int }) - -// See issue #49477: type parameters should not be interpreted as named types -// for the purpose of determining whether a function is a factory function. - -// Slice is not a factory function. -func Slice[T any]() []T { - return nil -} - -// Single is not a factory function. -func Single[T any]() *T { - return nil -} diff --git a/internal/backport/go/doc/testdata/issue12839.0.golden b/internal/backport/go/doc/testdata/issue12839.0.golden deleted file mode 100644 index 6b59774fb9..0000000000 --- a/internal/backport/go/doc/testdata/issue12839.0.golden +++ /dev/null @@ -1,51 +0,0 @@ -// Package issue12839 is a go/doc test to test association of a ... -PACKAGE issue12839 - -IMPORTPATH - testdata/issue12839 - -IMPORTS - p - -FILENAMES - testdata/issue12839.go - -FUNCTIONS - // F1 should not be associated with T1 - func F1() (*T1, *T2) - - // F10 should not be associated with T1. - func F10() (T1, T2, error) - - // F4 should not be associated with a type (same as F1) - func F4() (a T1, b T2) - - // F9 should not be associated with T1. - func F9() (int, T1, T2) - - -TYPES - // - type T1 struct{} - - // F2 should be associated with T1 - func F2() (a, b, c T1) - - // F3 should be associated with T1 because b.T3 is from a ... - func F3() (a T1, b p.T3) - - // F5 should be associated with T1. - func F5() (T1, error) - - // F6 should be associated with T1. - func F6() (*T1, error) - - // F7 should be associated with T1. - func F7() (T1, string) - - // F8 should be associated with T1. - func F8() (int, T1, string) - - // - type T2 struct{} - diff --git a/internal/backport/go/doc/testdata/issue12839.1.golden b/internal/backport/go/doc/testdata/issue12839.1.golden deleted file mode 100644 index 4b9b9f6477..0000000000 --- a/internal/backport/go/doc/testdata/issue12839.1.golden +++ /dev/null @@ -1,54 +0,0 @@ -// Package issue12839 is a go/doc test to test association of a ... -PACKAGE issue12839 - -IMPORTPATH - testdata/issue12839 - -IMPORTS - p - -FILENAMES - testdata/issue12839.go - -FUNCTIONS - // F1 should not be associated with T1 - func F1() (*T1, *T2) - - // F10 should not be associated with T1. - func F10() (T1, T2, error) - - // F4 should not be associated with a type (same as F1) - func F4() (a T1, b T2) - - // F9 should not be associated with T1. - func F9() (int, T1, T2) - - -TYPES - // - type T1 struct{} - - // F2 should be associated with T1 - func F2() (a, b, c T1) - - // F3 should be associated with T1 because b.T3 is from a ... - func F3() (a T1, b p.T3) - - // F5 should be associated with T1. - func F5() (T1, error) - - // F6 should be associated with T1. - func F6() (*T1, error) - - // F7 should be associated with T1. - func F7() (T1, string) - - // F8 should be associated with T1. - func F8() (int, T1, string) - - // - func (t T1) hello() string - - // - type T2 struct{} - diff --git a/internal/backport/go/doc/testdata/issue12839.2.golden b/internal/backport/go/doc/testdata/issue12839.2.golden deleted file mode 100644 index 6b59774fb9..0000000000 --- a/internal/backport/go/doc/testdata/issue12839.2.golden +++ /dev/null @@ -1,51 +0,0 @@ -// Package issue12839 is a go/doc test to test association of a ... -PACKAGE issue12839 - -IMPORTPATH - testdata/issue12839 - -IMPORTS - p - -FILENAMES - testdata/issue12839.go - -FUNCTIONS - // F1 should not be associated with T1 - func F1() (*T1, *T2) - - // F10 should not be associated with T1. - func F10() (T1, T2, error) - - // F4 should not be associated with a type (same as F1) - func F4() (a T1, b T2) - - // F9 should not be associated with T1. - func F9() (int, T1, T2) - - -TYPES - // - type T1 struct{} - - // F2 should be associated with T1 - func F2() (a, b, c T1) - - // F3 should be associated with T1 because b.T3 is from a ... - func F3() (a T1, b p.T3) - - // F5 should be associated with T1. - func F5() (T1, error) - - // F6 should be associated with T1. - func F6() (*T1, error) - - // F7 should be associated with T1. - func F7() (T1, string) - - // F8 should be associated with T1. - func F8() (int, T1, string) - - // - type T2 struct{} - diff --git a/internal/backport/go/doc/testdata/issue12839.go b/internal/backport/go/doc/testdata/issue12839.go deleted file mode 100644 index 51c7ac1268..0000000000 --- a/internal/backport/go/doc/testdata/issue12839.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2018 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 issue12839 is a go/doc test to test association of a function -// that returns multiple types. -// See golang.org/issue/12839. -// (See also golang.org/issue/27928.) -package issue12839 - -import "p" - -type T1 struct{} - -type T2 struct{} - -func (t T1) hello() string { - return "hello" -} - -// F1 should not be associated with T1 -func F1() (*T1, *T2) { - return &T1{}, &T2{} -} - -// F2 should be associated with T1 -func F2() (a, b, c T1) { - return T1{}, T1{}, T1{} -} - -// F3 should be associated with T1 because b.T3 is from a different package -func F3() (a T1, b p.T3) { - return T1{}, p.T3{} -} - -// F4 should not be associated with a type (same as F1) -func F4() (a T1, b T2) { - return T1{}, T2{} -} - -// F5 should be associated with T1. -func F5() (T1, error) { - return T1{}, nil -} - -// F6 should be associated with T1. -func F6() (*T1, error) { - return &T1{}, nil -} - -// F7 should be associated with T1. -func F7() (T1, string) { - return T1{}, nil -} - -// F8 should be associated with T1. -func F8() (int, T1, string) { - return 0, T1{}, nil -} - -// F9 should not be associated with T1. -func F9() (int, T1, T2) { - return 0, T1{}, T2{} -} - -// F10 should not be associated with T1. -func F10() (T1, T2, error) { - return T1{}, T2{}, nil -} diff --git a/internal/backport/go/doc/testdata/issue13742.0.golden b/internal/backport/go/doc/testdata/issue13742.0.golden deleted file mode 100644 index 8dee9aaa80..0000000000 --- a/internal/backport/go/doc/testdata/issue13742.0.golden +++ /dev/null @@ -1,25 +0,0 @@ -// -PACKAGE issue13742 - -IMPORTPATH - testdata/issue13742 - -IMPORTS - go/ast - -FILENAMES - testdata/issue13742.go - -FUNCTIONS - // Both F0 and G0 should appear as functions. - func F0(Node) - - // Both F1 and G1 should appear as functions. - func F1(ast.Node) - - // - func G0() Node - - // - func G1() ast.Node - diff --git a/internal/backport/go/doc/testdata/issue13742.1.golden b/internal/backport/go/doc/testdata/issue13742.1.golden deleted file mode 100644 index 8dee9aaa80..0000000000 --- a/internal/backport/go/doc/testdata/issue13742.1.golden +++ /dev/null @@ -1,25 +0,0 @@ -// -PACKAGE issue13742 - -IMPORTPATH - testdata/issue13742 - -IMPORTS - go/ast - -FILENAMES - testdata/issue13742.go - -FUNCTIONS - // Both F0 and G0 should appear as functions. - func F0(Node) - - // Both F1 and G1 should appear as functions. - func F1(ast.Node) - - // - func G0() Node - - // - func G1() ast.Node - diff --git a/internal/backport/go/doc/testdata/issue13742.2.golden b/internal/backport/go/doc/testdata/issue13742.2.golden deleted file mode 100644 index 8dee9aaa80..0000000000 --- a/internal/backport/go/doc/testdata/issue13742.2.golden +++ /dev/null @@ -1,25 +0,0 @@ -// -PACKAGE issue13742 - -IMPORTPATH - testdata/issue13742 - -IMPORTS - go/ast - -FILENAMES - testdata/issue13742.go - -FUNCTIONS - // Both F0 and G0 should appear as functions. - func F0(Node) - - // Both F1 and G1 should appear as functions. - func F1(ast.Node) - - // - func G0() Node - - // - func G1() ast.Node - diff --git a/internal/backport/go/doc/testdata/issue13742.go b/internal/backport/go/doc/testdata/issue13742.go deleted file mode 100644 index dbc19411a6..0000000000 --- a/internal/backport/go/doc/testdata/issue13742.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2016 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 issue13742 - -import ( - "go/ast" - . "go/ast" -) - -// Both F0 and G0 should appear as functions. -func F0(Node) {} -func G0() Node { return nil } - -// Both F1 and G1 should appear as functions. -func F1(ast.Node) {} -func G1() ast.Node { return nil } diff --git a/internal/backport/go/doc/testdata/issue16153.0.golden b/internal/backport/go/doc/testdata/issue16153.0.golden deleted file mode 100644 index 189260b4d0..0000000000 --- a/internal/backport/go/doc/testdata/issue16153.0.golden +++ /dev/null @@ -1,32 +0,0 @@ -// -PACKAGE issue16153 - -IMPORTPATH - testdata/issue16153 - -FILENAMES - testdata/issue16153.go - -CONSTANTS - // - const ( - X3 int64 = iota - Y3 = 1 - ) - - // - const ( - X4 int64 = iota - Y4 - ) - - // original test case - const ( - Y1 = 256 - ) - - // variations - const ( - Y2 uint8 - ) - diff --git a/internal/backport/go/doc/testdata/issue16153.1.golden b/internal/backport/go/doc/testdata/issue16153.1.golden deleted file mode 100644 index 803df3e709..0000000000 --- a/internal/backport/go/doc/testdata/issue16153.1.golden +++ /dev/null @@ -1,34 +0,0 @@ -// -PACKAGE issue16153 - -IMPORTPATH - testdata/issue16153 - -FILENAMES - testdata/issue16153.go - -CONSTANTS - // original test case - const ( - x1 uint8 = 255 - Y1 = 256 - ) - - // variations - const ( - x2 uint8 = 255 - Y2 - ) - - // - const ( - X3 int64 = iota - Y3 = 1 - ) - - // - const ( - X4 int64 = iota - Y4 - ) - diff --git a/internal/backport/go/doc/testdata/issue16153.2.golden b/internal/backport/go/doc/testdata/issue16153.2.golden deleted file mode 100644 index 189260b4d0..0000000000 --- a/internal/backport/go/doc/testdata/issue16153.2.golden +++ /dev/null @@ -1,32 +0,0 @@ -// -PACKAGE issue16153 - -IMPORTPATH - testdata/issue16153 - -FILENAMES - testdata/issue16153.go - -CONSTANTS - // - const ( - X3 int64 = iota - Y3 = 1 - ) - - // - const ( - X4 int64 = iota - Y4 - ) - - // original test case - const ( - Y1 = 256 - ) - - // variations - const ( - Y2 uint8 - ) - diff --git a/internal/backport/go/doc/testdata/issue16153.go b/internal/backport/go/doc/testdata/issue16153.go deleted file mode 100644 index 528be42356..0000000000 --- a/internal/backport/go/doc/testdata/issue16153.go +++ /dev/null @@ -1,27 +0,0 @@ -// 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 issue16153 - -// original test case -const ( - x1 uint8 = 255 - Y1 = 256 -) - -// variations -const ( - x2 uint8 = 255 - Y2 -) - -const ( - X3 int64 = iota - Y3 = 1 -) - -const ( - X4 int64 = iota - Y4 -) diff --git a/internal/backport/go/doc/testdata/issue17788.0.golden b/internal/backport/go/doc/testdata/issue17788.0.golden deleted file mode 100644 index 42c00da504..0000000000 --- a/internal/backport/go/doc/testdata/issue17788.0.golden +++ /dev/null @@ -1,8 +0,0 @@ -// -PACKAGE issue17788 - -IMPORTPATH - testdata/issue17788 - -FILENAMES - testdata/issue17788.go diff --git a/internal/backport/go/doc/testdata/issue17788.1.golden b/internal/backport/go/doc/testdata/issue17788.1.golden deleted file mode 100644 index 42c00da504..0000000000 --- a/internal/backport/go/doc/testdata/issue17788.1.golden +++ /dev/null @@ -1,8 +0,0 @@ -// -PACKAGE issue17788 - -IMPORTPATH - testdata/issue17788 - -FILENAMES - testdata/issue17788.go diff --git a/internal/backport/go/doc/testdata/issue17788.2.golden b/internal/backport/go/doc/testdata/issue17788.2.golden deleted file mode 100644 index 42c00da504..0000000000 --- a/internal/backport/go/doc/testdata/issue17788.2.golden +++ /dev/null @@ -1,8 +0,0 @@ -// -PACKAGE issue17788 - -IMPORTPATH - testdata/issue17788 - -FILENAMES - testdata/issue17788.go diff --git a/internal/backport/go/doc/testdata/issue17788.go b/internal/backport/go/doc/testdata/issue17788.go deleted file mode 100644 index 883ad5f769..0000000000 --- a/internal/backport/go/doc/testdata/issue17788.go +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2016 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 issue17788 - -func ( /* receiver type */ ) f0() { -} diff --git a/internal/backport/go/doc/testdata/issue22856.0.golden b/internal/backport/go/doc/testdata/issue22856.0.golden deleted file mode 100644 index a88f43f4bd..0000000000 --- a/internal/backport/go/doc/testdata/issue22856.0.golden +++ /dev/null @@ -1,45 +0,0 @@ -// -PACKAGE issue22856 - -IMPORTPATH - testdata/issue22856 - -FILENAMES - testdata/issue22856.go - -FUNCTIONS - // NewPointerSliceOfSlice is not a factory function because slices ... - func NewPointerSliceOfSlice() [][]*T - - // NewSlice3 is not a factory function because 3 nested slices of ... - func NewSlice3() [][][]T - - // NewSliceOfSlice is not a factory function because slices of a ... - func NewSliceOfSlice() [][]T - - -TYPES - // - type T struct{} - - // - func New() T - - // - func NewArray() [1]T - - // - func NewPointer() *T - - // - func NewPointerArray() [1]*T - - // - func NewPointerOfPointer() **T - - // - func NewPointerSlice() []*T - - // - func NewSlice() []T - diff --git a/internal/backport/go/doc/testdata/issue22856.1.golden b/internal/backport/go/doc/testdata/issue22856.1.golden deleted file mode 100644 index a88f43f4bd..0000000000 --- a/internal/backport/go/doc/testdata/issue22856.1.golden +++ /dev/null @@ -1,45 +0,0 @@ -// -PACKAGE issue22856 - -IMPORTPATH - testdata/issue22856 - -FILENAMES - testdata/issue22856.go - -FUNCTIONS - // NewPointerSliceOfSlice is not a factory function because slices ... - func NewPointerSliceOfSlice() [][]*T - - // NewSlice3 is not a factory function because 3 nested slices of ... - func NewSlice3() [][][]T - - // NewSliceOfSlice is not a factory function because slices of a ... - func NewSliceOfSlice() [][]T - - -TYPES - // - type T struct{} - - // - func New() T - - // - func NewArray() [1]T - - // - func NewPointer() *T - - // - func NewPointerArray() [1]*T - - // - func NewPointerOfPointer() **T - - // - func NewPointerSlice() []*T - - // - func NewSlice() []T - diff --git a/internal/backport/go/doc/testdata/issue22856.2.golden b/internal/backport/go/doc/testdata/issue22856.2.golden deleted file mode 100644 index a88f43f4bd..0000000000 --- a/internal/backport/go/doc/testdata/issue22856.2.golden +++ /dev/null @@ -1,45 +0,0 @@ -// -PACKAGE issue22856 - -IMPORTPATH - testdata/issue22856 - -FILENAMES - testdata/issue22856.go - -FUNCTIONS - // NewPointerSliceOfSlice is not a factory function because slices ... - func NewPointerSliceOfSlice() [][]*T - - // NewSlice3 is not a factory function because 3 nested slices of ... - func NewSlice3() [][][]T - - // NewSliceOfSlice is not a factory function because slices of a ... - func NewSliceOfSlice() [][]T - - -TYPES - // - type T struct{} - - // - func New() T - - // - func NewArray() [1]T - - // - func NewPointer() *T - - // - func NewPointerArray() [1]*T - - // - func NewPointerOfPointer() **T - - // - func NewPointerSlice() []*T - - // - func NewSlice() []T - diff --git a/internal/backport/go/doc/testdata/issue22856.go b/internal/backport/go/doc/testdata/issue22856.go deleted file mode 100644 index f4569981aa..0000000000 --- a/internal/backport/go/doc/testdata/issue22856.go +++ /dev/null @@ -1,27 +0,0 @@ -// 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 issue22856 - -type T struct{} - -func New() T { return T{} } -func NewPointer() *T { return &T{} } -func NewPointerSlice() []*T { return []*T{&T{}} } -func NewSlice() []T { return []T{T{}} } -func NewPointerOfPointer() **T { x := &T{}; return &x } -func NewArray() [1]T { return [1]T{T{}} } -func NewPointerArray() [1]*T { return [1]*T{&T{}} } - -// NewSliceOfSlice is not a factory function because slices of a slice of -// type *T are not factory functions of type T. -func NewSliceOfSlice() [][]T { return []T{[]T{}} } - -// NewPointerSliceOfSlice is not a factory function because slices of a -// slice of type *T are not factory functions of type T. -func NewPointerSliceOfSlice() [][]*T { return []*T{[]*T{}} } - -// NewSlice3 is not a factory function because 3 nested slices of type T -// are not factory functions of type T. -func NewSlice3() [][][]T { return []T{[]T{[]T{}}} } diff --git a/internal/backport/go/doc/testdata/predeclared.0.golden b/internal/backport/go/doc/testdata/predeclared.0.golden deleted file mode 100644 index 9f37b069f0..0000000000 --- a/internal/backport/go/doc/testdata/predeclared.0.golden +++ /dev/null @@ -1,8 +0,0 @@ -// Package predeclared is a go/doc test for handling of exported ... -PACKAGE predeclared - -IMPORTPATH - testdata/predeclared - -FILENAMES - testdata/predeclared.go diff --git a/internal/backport/go/doc/testdata/predeclared.1.golden b/internal/backport/go/doc/testdata/predeclared.1.golden deleted file mode 100644 index 2ff8ee666b..0000000000 --- a/internal/backport/go/doc/testdata/predeclared.1.golden +++ /dev/null @@ -1,22 +0,0 @@ -// Package predeclared is a go/doc test for handling of exported ... -PACKAGE predeclared - -IMPORTPATH - testdata/predeclared - -FILENAMES - testdata/predeclared.go - -TYPES - // - type bool int - - // Must not be visible. - func (b bool) String() string - - // - type error struct{} - - // Must not be visible. - func (e error) Error() string - diff --git a/internal/backport/go/doc/testdata/predeclared.2.golden b/internal/backport/go/doc/testdata/predeclared.2.golden deleted file mode 100644 index 9f37b069f0..0000000000 --- a/internal/backport/go/doc/testdata/predeclared.2.golden +++ /dev/null @@ -1,8 +0,0 @@ -// Package predeclared is a go/doc test for handling of exported ... -PACKAGE predeclared - -IMPORTPATH - testdata/predeclared - -FILENAMES - testdata/predeclared.go diff --git a/internal/backport/go/doc/testdata/predeclared.go b/internal/backport/go/doc/testdata/predeclared.go deleted file mode 100644 index c6dd806cba..0000000000 --- a/internal/backport/go/doc/testdata/predeclared.go +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2016 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 predeclared is a go/doc test for handling of -// exported methods on locally-defined predeclared types. -// See issue 9860. -package predeclared - -type error struct{} - -// Must not be visible. -func (e error) Error() string { - return "" -} - -type bool int - -// Must not be visible. -func (b bool) String() string { - return "" -} diff --git a/internal/backport/go/doc/testdata/template.txt b/internal/backport/go/doc/testdata/template.txt deleted file mode 100644 index 1b07382611..0000000000 --- a/internal/backport/go/doc/testdata/template.txt +++ /dev/null @@ -1,68 +0,0 @@ -{{synopsis .Doc}} -PACKAGE {{.Name}} - -IMPORTPATH - {{.ImportPath}} - -{{with .Imports}}IMPORTS -{{range .}} {{.}} -{{end}} -{{end}}{{/* - -*/}}FILENAMES -{{range .Filenames}} {{.}} -{{end}}{{/* - -*/}}{{with .Consts}} -CONSTANTS -{{range .}} {{synopsis .Doc}} - {{node .Decl $.FSet}} - -{{end}}{{end}}{{/* - -*/}}{{with .Vars}} -VARIABLES -{{range .}} {{synopsis .Doc}} - {{node .Decl $.FSet}} - -{{end}}{{end}}{{/* - -*/}}{{with .Funcs}} -FUNCTIONS -{{range .}} {{synopsis .Doc}} - {{node .Decl $.FSet}} - -{{end}}{{end}}{{/* - -*/}}{{with .Types}} -TYPES -{{range .}} {{synopsis .Doc}} - {{node .Decl $.FSet}} - -{{range .Consts}} {{synopsis .Doc}} - {{node .Decl $.FSet}} - -{{end}}{{/* - -*/}}{{range .Vars}} {{synopsis .Doc}} - {{node .Decl $.FSet}} - -{{end}}{{/* - -*/}}{{range .Funcs}} {{synopsis .Doc}} - {{node .Decl $.FSet}} - -{{end}}{{/* - -*/}}{{range .Methods}} {{synopsis .Doc}} - {{node .Decl $.FSet}} - -{{end}}{{end}}{{end}}{{/* - -*/}}{{with .Bugs}} -BUGS .Bugs is now deprecated, please use .Notes instead -{{range .}}{{indent "\t" .}} -{{end}}{{end}}{{with .Notes}}{{range $marker, $content := .}} -{{$marker}}S -{{range $content}}{{$marker}}({{.UID}}){{indent "\t" .Body}} -{{end}}{{end}}{{end}} \ No newline at end of file diff --git a/internal/backport/go/doc/testdata/testing.0.golden b/internal/backport/go/doc/testdata/testing.0.golden deleted file mode 100644 index 61dac8bb66..0000000000 --- a/internal/backport/go/doc/testdata/testing.0.golden +++ /dev/null @@ -1,156 +0,0 @@ -// Package testing provides support for automated testing of Go ... -PACKAGE testing - -IMPORTPATH - testdata/testing - -IMPORTS - bytes - flag - fmt - io - os - runtime - runtime/pprof - strconv - strings - time - -FILENAMES - testdata/benchmark.go - testdata/example.go - testdata/testing.go - -FUNCTIONS - // An internal function but exported because it is cross-package; ... - func Main(matchString func(pat, str string) (bool, error), tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample) - - // An internal function but exported because it is cross-package; ... - func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark) - - // - func RunExamples(examples []InternalExample) (ok bool) - - // - func RunTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ok bool) - - // Short reports whether the -test.short flag is set. - func Short() bool - - -TYPES - // B is a type passed to Benchmark functions to manage benchmark ... - type B struct { - N int - // contains filtered or unexported fields - } - - // Error is equivalent to Log() followed by Fail(). - func (c *B) Error(args ...any) - - // Errorf is equivalent to Logf() followed by Fail(). - func (c *B) Errorf(format string, args ...any) - - // Fail marks the function as having failed but continues ... - func (c *B) Fail() - - // FailNow marks the function as having failed and stops its ... - func (c *B) FailNow() - - // Failed reports whether the function has failed. - func (c *B) Failed() bool - - // Fatal is equivalent to Log() followed by FailNow(). - func (c *B) Fatal(args ...any) - - // Fatalf is equivalent to Logf() followed by FailNow(). - func (c *B) Fatalf(format string, args ...any) - - // Log formats its arguments using default formatting, analogous ... - func (c *B) Log(args ...any) - - // Logf formats its arguments according to the format, analogous ... - func (c *B) Logf(format string, args ...any) - - // ResetTimer sets the elapsed benchmark time to zero. It does not ... - func (b *B) ResetTimer() - - // SetBytes records the number of bytes processed in a single ... - func (b *B) SetBytes(n int64) - - // StartTimer starts timing a test. This function is called ... - func (b *B) StartTimer() - - // StopTimer stops timing a test. This can be used to pause the ... - func (b *B) StopTimer() - - // The results of a benchmark run. - type BenchmarkResult struct { - N int // The number of iterations. - T time.Duration // The total time taken. - Bytes int64 // Bytes processed in one iteration. - } - - // Benchmark benchmarks a single function. Useful for creating ... - func Benchmark(f func(b *B)) BenchmarkResult - - // - func (r BenchmarkResult) NsPerOp() int64 - - // - func (r BenchmarkResult) String() string - - // An internal type but exported because it is cross-package; part ... - type InternalBenchmark struct { - Name string - F func(b *B) - } - - // - type InternalExample struct { - Name string - F func() - Output string - } - - // An internal type but exported because it is cross-package; part ... - type InternalTest struct { - Name string - F func(*T) - } - - // T is a type passed to Test functions to manage test state and ... - type T struct { - // contains filtered or unexported fields - } - - // Error is equivalent to Log() followed by Fail(). - func (c *T) Error(args ...any) - - // Errorf is equivalent to Logf() followed by Fail(). - func (c *T) Errorf(format string, args ...any) - - // Fail marks the function as having failed but continues ... - func (c *T) Fail() - - // FailNow marks the function as having failed and stops its ... - func (c *T) FailNow() - - // Failed reports whether the function has failed. - func (c *T) Failed() bool - - // Fatal is equivalent to Log() followed by FailNow(). - func (c *T) Fatal(args ...any) - - // Fatalf is equivalent to Logf() followed by FailNow(). - func (c *T) Fatalf(format string, args ...any) - - // Log formats its arguments using default formatting, analogous ... - func (c *T) Log(args ...any) - - // Logf formats its arguments according to the format, analogous ... - func (c *T) Logf(format string, args ...any) - - // Parallel signals that this test is to be run in parallel with ... - func (t *T) Parallel() - diff --git a/internal/backport/go/doc/testdata/testing.1.golden b/internal/backport/go/doc/testdata/testing.1.golden deleted file mode 100644 index 1655af11a8..0000000000 --- a/internal/backport/go/doc/testdata/testing.1.golden +++ /dev/null @@ -1,298 +0,0 @@ -// Package testing provides support for automated testing of Go ... -PACKAGE testing - -IMPORTPATH - testdata/testing - -IMPORTS - bytes - flag - fmt - io - os - runtime - runtime/pprof - strconv - strings - time - -FILENAMES - testdata/benchmark.go - testdata/example.go - testdata/testing.go - -VARIABLES - // - var ( - // The short flag requests that tests run more quickly, but its functionality - // is provided by test writers themselves. The testing package is just its - // home. The all.bash installation script sets it to make installation more - // efficient, but by default the flag is off so a plain "go test" will do a - // full test of the package. - short = flag.Bool("test.short", false, "run smaller test suite to save time") - - // Report as tests are run; default is silent for success. - chatty = flag.Bool("test.v", false, "verbose: print additional output") - match = flag.String("test.run", "", "regular expression to select tests to run") - memProfile = flag.String("test.memprofile", "", "write a memory profile to the named file after execution") - memProfileRate = flag.Int("test.memprofilerate", 0, "if >=0, sets runtime.MemProfileRate") - cpuProfile = flag.String("test.cpuprofile", "", "write a cpu profile to the named file during execution") - timeout = flag.Duration("test.timeout", 0, "if positive, sets an aggregate time limit for all tests") - cpuListStr = flag.String("test.cpu", "", "comma-separated list of number of CPUs to use for each test") - parallel = flag.Int("test.parallel", runtime.GOMAXPROCS(0), "maximum test parallelism") - - cpuList []int - ) - - // - var benchTime = flag.Duration("test.benchtime", 1*time.Second, "approximate run time for each benchmark") - - // - var matchBenchmarks = flag.String("test.bench", "", "regular expression to select benchmarks to run") - - // - var timer *time.Timer - - -FUNCTIONS - // An internal function but exported because it is cross-package; ... - func Main(matchString func(pat, str string) (bool, error), tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample) - - // An internal function but exported because it is cross-package; ... - func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark) - - // - func RunExamples(examples []InternalExample) (ok bool) - - // - func RunTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ok bool) - - // Short reports whether the -test.short flag is set. - func Short() bool - - // after runs after all testing. - func after() - - // alarm is called if the timeout expires. - func alarm() - - // before runs before all testing. - func before() - - // decorate inserts the final newline if needed and indentation ... - func decorate(s string, addFileLine bool) string - - // - func max(x, y int) int - - // - func min(x, y int) int - - // - func parseCpuList() - - // roundDown10 rounds a number down to the nearest power of 10. - func roundDown10(n int) int - - // roundUp rounds x up to a number of the form [1eX, 2eX, 5eX]. - func roundUp(n int) int - - // startAlarm starts an alarm if requested. - func startAlarm() - - // stopAlarm turns off the alarm. - func stopAlarm() - - // - func tRunner(t *T, test *InternalTest) - - -TYPES - // B is a type passed to Benchmark functions to manage benchmark ... - type B struct { - common - N int - benchmark InternalBenchmark - bytes int64 - timerOn bool - result BenchmarkResult - } - - // Error is equivalent to Log() followed by Fail(). - func (c *B) Error(args ...any) - - // Errorf is equivalent to Logf() followed by Fail(). - func (c *B) Errorf(format string, args ...any) - - // Fail marks the function as having failed but continues ... - func (c *B) Fail() - - // FailNow marks the function as having failed and stops its ... - func (c *B) FailNow() - - // Failed reports whether the function has failed. - func (c *B) Failed() bool - - // Fatal is equivalent to Log() followed by FailNow(). - func (c *B) Fatal(args ...any) - - // Fatalf is equivalent to Logf() followed by FailNow(). - func (c *B) Fatalf(format string, args ...any) - - // Log formats its arguments using default formatting, analogous ... - func (c *B) Log(args ...any) - - // Logf formats its arguments according to the format, analogous ... - func (c *B) Logf(format string, args ...any) - - // ResetTimer sets the elapsed benchmark time to zero. It does not ... - func (b *B) ResetTimer() - - // SetBytes records the number of bytes processed in a single ... - func (b *B) SetBytes(n int64) - - // StartTimer starts timing a test. This function is called ... - func (b *B) StartTimer() - - // StopTimer stops timing a test. This can be used to pause the ... - func (b *B) StopTimer() - - // launch launches the benchmark function. It gradually increases ... - func (b *B) launch() - - // log generates the output. It's always at the same stack depth. - func (c *B) log(s string) - - // - func (b *B) nsPerOp() int64 - - // run times the benchmark function in a separate goroutine. - func (b *B) run() BenchmarkResult - - // runN runs a single benchmark for the specified number of ... - func (b *B) runN(n int) - - // trimOutput shortens the output from a benchmark, which can be ... - func (b *B) trimOutput() - - // The results of a benchmark run. - type BenchmarkResult struct { - N int // The number of iterations. - T time.Duration // The total time taken. - Bytes int64 // Bytes processed in one iteration. - } - - // Benchmark benchmarks a single function. Useful for creating ... - func Benchmark(f func(b *B)) BenchmarkResult - - // - func (r BenchmarkResult) NsPerOp() int64 - - // - func (r BenchmarkResult) String() string - - // - func (r BenchmarkResult) mbPerSec() float64 - - // An internal type but exported because it is cross-package; part ... - type InternalBenchmark struct { - Name string - F func(b *B) - } - - // - type InternalExample struct { - Name string - F func() - Output string - } - - // An internal type but exported because it is cross-package; part ... - type InternalTest struct { - Name string - F func(*T) - } - - // T is a type passed to Test functions to manage test state and ... - type T struct { - common - name string // Name of test. - startParallel chan bool // Parallel tests will wait on this. - } - - // Error is equivalent to Log() followed by Fail(). - func (c *T) Error(args ...any) - - // Errorf is equivalent to Logf() followed by Fail(). - func (c *T) Errorf(format string, args ...any) - - // Fail marks the function as having failed but continues ... - func (c *T) Fail() - - // FailNow marks the function as having failed and stops its ... - func (c *T) FailNow() - - // Failed reports whether the function has failed. - func (c *T) Failed() bool - - // Fatal is equivalent to Log() followed by FailNow(). - func (c *T) Fatal(args ...any) - - // Fatalf is equivalent to Logf() followed by FailNow(). - func (c *T) Fatalf(format string, args ...any) - - // Log formats its arguments using default formatting, analogous ... - func (c *T) Log(args ...any) - - // Logf formats its arguments according to the format, analogous ... - func (c *T) Logf(format string, args ...any) - - // Parallel signals that this test is to be run in parallel with ... - func (t *T) Parallel() - - // log generates the output. It's always at the same stack depth. - func (c *T) log(s string) - - // - func (t *T) report() - - // common holds the elements common between T and B and captures ... - type common struct { - output []byte // Output generated by test or benchmark. - failed bool // Test or benchmark has failed. - start time.Time // Time test or benchmark started - duration time.Duration - self any // To be sent on signal channel when done. - signal chan any // Output for serial tests. - } - - // Error is equivalent to Log() followed by Fail(). - func (c *common) Error(args ...any) - - // Errorf is equivalent to Logf() followed by Fail(). - func (c *common) Errorf(format string, args ...any) - - // Fail marks the function as having failed but continues ... - func (c *common) Fail() - - // FailNow marks the function as having failed and stops its ... - func (c *common) FailNow() - - // Failed reports whether the function has failed. - func (c *common) Failed() bool - - // Fatal is equivalent to Log() followed by FailNow(). - func (c *common) Fatal(args ...any) - - // Fatalf is equivalent to Logf() followed by FailNow(). - func (c *common) Fatalf(format string, args ...any) - - // Log formats its arguments using default formatting, analogous ... - func (c *common) Log(args ...any) - - // Logf formats its arguments according to the format, analogous ... - func (c *common) Logf(format string, args ...any) - - // log generates the output. It's always at the same stack depth. - func (c *common) log(s string) - diff --git a/internal/backport/go/doc/testdata/testing.2.golden b/internal/backport/go/doc/testdata/testing.2.golden deleted file mode 100644 index 61dac8bb66..0000000000 --- a/internal/backport/go/doc/testdata/testing.2.golden +++ /dev/null @@ -1,156 +0,0 @@ -// Package testing provides support for automated testing of Go ... -PACKAGE testing - -IMPORTPATH - testdata/testing - -IMPORTS - bytes - flag - fmt - io - os - runtime - runtime/pprof - strconv - strings - time - -FILENAMES - testdata/benchmark.go - testdata/example.go - testdata/testing.go - -FUNCTIONS - // An internal function but exported because it is cross-package; ... - func Main(matchString func(pat, str string) (bool, error), tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample) - - // An internal function but exported because it is cross-package; ... - func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark) - - // - func RunExamples(examples []InternalExample) (ok bool) - - // - func RunTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ok bool) - - // Short reports whether the -test.short flag is set. - func Short() bool - - -TYPES - // B is a type passed to Benchmark functions to manage benchmark ... - type B struct { - N int - // contains filtered or unexported fields - } - - // Error is equivalent to Log() followed by Fail(). - func (c *B) Error(args ...any) - - // Errorf is equivalent to Logf() followed by Fail(). - func (c *B) Errorf(format string, args ...any) - - // Fail marks the function as having failed but continues ... - func (c *B) Fail() - - // FailNow marks the function as having failed and stops its ... - func (c *B) FailNow() - - // Failed reports whether the function has failed. - func (c *B) Failed() bool - - // Fatal is equivalent to Log() followed by FailNow(). - func (c *B) Fatal(args ...any) - - // Fatalf is equivalent to Logf() followed by FailNow(). - func (c *B) Fatalf(format string, args ...any) - - // Log formats its arguments using default formatting, analogous ... - func (c *B) Log(args ...any) - - // Logf formats its arguments according to the format, analogous ... - func (c *B) Logf(format string, args ...any) - - // ResetTimer sets the elapsed benchmark time to zero. It does not ... - func (b *B) ResetTimer() - - // SetBytes records the number of bytes processed in a single ... - func (b *B) SetBytes(n int64) - - // StartTimer starts timing a test. This function is called ... - func (b *B) StartTimer() - - // StopTimer stops timing a test. This can be used to pause the ... - func (b *B) StopTimer() - - // The results of a benchmark run. - type BenchmarkResult struct { - N int // The number of iterations. - T time.Duration // The total time taken. - Bytes int64 // Bytes processed in one iteration. - } - - // Benchmark benchmarks a single function. Useful for creating ... - func Benchmark(f func(b *B)) BenchmarkResult - - // - func (r BenchmarkResult) NsPerOp() int64 - - // - func (r BenchmarkResult) String() string - - // An internal type but exported because it is cross-package; part ... - type InternalBenchmark struct { - Name string - F func(b *B) - } - - // - type InternalExample struct { - Name string - F func() - Output string - } - - // An internal type but exported because it is cross-package; part ... - type InternalTest struct { - Name string - F func(*T) - } - - // T is a type passed to Test functions to manage test state and ... - type T struct { - // contains filtered or unexported fields - } - - // Error is equivalent to Log() followed by Fail(). - func (c *T) Error(args ...any) - - // Errorf is equivalent to Logf() followed by Fail(). - func (c *T) Errorf(format string, args ...any) - - // Fail marks the function as having failed but continues ... - func (c *T) Fail() - - // FailNow marks the function as having failed and stops its ... - func (c *T) FailNow() - - // Failed reports whether the function has failed. - func (c *T) Failed() bool - - // Fatal is equivalent to Log() followed by FailNow(). - func (c *T) Fatal(args ...any) - - // Fatalf is equivalent to Logf() followed by FailNow(). - func (c *T) Fatalf(format string, args ...any) - - // Log formats its arguments using default formatting, analogous ... - func (c *T) Log(args ...any) - - // Logf formats its arguments according to the format, analogous ... - func (c *T) Logf(format string, args ...any) - - // Parallel signals that this test is to be run in parallel with ... - func (t *T) Parallel() - diff --git a/internal/backport/go/doc/testdata/testing.go b/internal/backport/go/doc/testdata/testing.go deleted file mode 100644 index 6365ffceed..0000000000 --- a/internal/backport/go/doc/testdata/testing.go +++ /dev/null @@ -1,404 +0,0 @@ -// Copyright 2009 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 testing provides support for automated testing of Go packages. -// It is intended to be used in concert with the “go test” utility, which automates -// execution of any function of the form -// func TestXxx(*testing.T) -// where Xxx can be any alphanumeric string (but the first letter must not be in -// [a-z]) and serves to identify the test routine. -// These TestXxx routines should be declared within the package they are testing. -// -// Functions of the form -// func BenchmarkXxx(*testing.B) -// are considered benchmarks, and are executed by go test when the -test.bench -// flag is provided. -// -// A sample benchmark function looks like this: -// func BenchmarkHello(b *testing.B) { -// for i := 0; i < b.N; i++ { -// fmt.Sprintf("hello") -// } -// } -// The benchmark package will vary b.N until the benchmark function lasts -// long enough to be timed reliably. The output -// testing.BenchmarkHello 10000000 282 ns/op -// means that the loop ran 10000000 times at a speed of 282 ns per loop. -// -// If a benchmark needs some expensive setup before running, the timer -// may be stopped: -// func BenchmarkBigLen(b *testing.B) { -// b.StopTimer() -// big := NewBig() -// b.StartTimer() -// for i := 0; i < b.N; i++ { -// big.Len() -// } -// } -package testing - -import ( - "flag" - "fmt" - "os" - "runtime" - "runtime/pprof" - "strconv" - "strings" - "time" -) - -var ( - // The short flag requests that tests run more quickly, but its functionality - // is provided by test writers themselves. The testing package is just its - // home. The all.bash installation script sets it to make installation more - // efficient, but by default the flag is off so a plain "go test" will do a - // full test of the package. - short = flag.Bool("test.short", false, "run smaller test suite to save time") - - // Report as tests are run; default is silent for success. - chatty = flag.Bool("test.v", false, "verbose: print additional output") - match = flag.String("test.run", "", "regular expression to select tests to run") - memProfile = flag.String("test.memprofile", "", "write a memory profile to the named file after execution") - memProfileRate = flag.Int("test.memprofilerate", 0, "if >=0, sets runtime.MemProfileRate") - cpuProfile = flag.String("test.cpuprofile", "", "write a cpu profile to the named file during execution") - timeout = flag.Duration("test.timeout", 0, "if positive, sets an aggregate time limit for all tests") - cpuListStr = flag.String("test.cpu", "", "comma-separated list of number of CPUs to use for each test") - parallel = flag.Int("test.parallel", runtime.GOMAXPROCS(0), "maximum test parallelism") - - cpuList []int -) - -// common holds the elements common between T and B and -// captures common methods such as Errorf. -type common struct { - output []byte // Output generated by test or benchmark. - failed bool // Test or benchmark has failed. - start time.Time // Time test or benchmark started - duration time.Duration - self any // To be sent on signal channel when done. - signal chan any // Output for serial tests. -} - -// Short reports whether the -test.short flag is set. -func Short() bool { - return *short -} - -// decorate inserts the final newline if needed and indentation tabs for formatting. -// If addFileLine is true, it also prefixes the string with the file and line of the call site. -func decorate(s string, addFileLine bool) string { - if addFileLine { - _, file, line, ok := runtime.Caller(3) // decorate + log + public function. - if ok { - // Truncate file name at last file name separator. - if index := strings.LastIndex(file, "/"); index >= 0 { - file = file[index+1:] - } else if index = strings.LastIndex(file, "\\"); index >= 0 { - file = file[index+1:] - } - } else { - file = "???" - line = 1 - } - s = fmt.Sprintf("%s:%d: %s", file, line, s) - } - s = "\t" + s // Every line is indented at least one tab. - n := len(s) - if n > 0 && s[n-1] != '\n' { - s += "\n" - n++ - } - for i := 0; i < n-1; i++ { // -1 to avoid final newline - if s[i] == '\n' { - // Second and subsequent lines are indented an extra tab. - return s[0:i+1] + "\t" + decorate(s[i+1:n], false) - } - } - return s -} - -// T is a type passed to Test functions to manage test state and support formatted test logs. -// Logs are accumulated during execution and dumped to standard error when done. -type T struct { - common - name string // Name of test. - startParallel chan bool // Parallel tests will wait on this. -} - -// Fail marks the function as having failed but continues execution. -func (c *common) Fail() { c.failed = true } - -// Failed reports whether the function has failed. -func (c *common) Failed() bool { return c.failed } - -// FailNow marks the function as having failed and stops its execution. -// Execution will continue at the next Test. -func (c *common) FailNow() { - c.Fail() - - // Calling runtime.Goexit will exit the goroutine, which - // will run the deferred functions in this goroutine, - // which will eventually run the deferred lines in tRunner, - // which will signal to the test loop that this test is done. - // - // A previous version of this code said: - // - // c.duration = ... - // c.signal <- c.self - // runtime.Goexit() - // - // This previous version duplicated code (those lines are in - // tRunner no matter what), but worse the goroutine teardown - // implicit in runtime.Goexit was not guaranteed to complete - // before the test exited. If a test deferred an important cleanup - // function (like removing temporary files), there was no guarantee - // it would run on a test failure. Because we send on c.signal during - // a top-of-stack deferred function now, we know that the send - // only happens after any other stacked defers have completed. - runtime.Goexit() -} - -// log generates the output. It's always at the same stack depth. -func (c *common) log(s string) { - c.output = append(c.output, decorate(s, true)...) -} - -// Log formats its arguments using default formatting, analogous to Println(), -// and records the text in the error log. -func (c *common) Log(args ...any) { c.log(fmt.Sprintln(args...)) } - -// Logf formats its arguments according to the format, analogous to Printf(), -// and records the text in the error log. -func (c *common) Logf(format string, args ...any) { c.log(fmt.Sprintf(format, args...)) } - -// Error is equivalent to Log() followed by Fail(). -func (c *common) Error(args ...any) { - c.log(fmt.Sprintln(args...)) - c.Fail() -} - -// Errorf is equivalent to Logf() followed by Fail(). -func (c *common) Errorf(format string, args ...any) { - c.log(fmt.Sprintf(format, args...)) - c.Fail() -} - -// Fatal is equivalent to Log() followed by FailNow(). -func (c *common) Fatal(args ...any) { - c.log(fmt.Sprintln(args...)) - c.FailNow() -} - -// Fatalf is equivalent to Logf() followed by FailNow(). -func (c *common) Fatalf(format string, args ...any) { - c.log(fmt.Sprintf(format, args...)) - c.FailNow() -} - -// Parallel signals that this test is to be run in parallel with (and only with) -// other parallel tests in this CPU group. -func (t *T) Parallel() { - t.signal <- (*T)(nil) // Release main testing loop - <-t.startParallel // Wait for serial tests to finish -} - -// An internal type but exported because it is cross-package; part of the implementation -// of go test. -type InternalTest struct { - Name string - F func(*T) -} - -func tRunner(t *T, test *InternalTest) { - t.start = time.Now() - - // When this goroutine is done, either because test.F(t) - // returned normally or because a test failure triggered - // a call to runtime.Goexit, record the duration and send - // a signal saying that the test is done. - defer func() { - t.duration = time.Now().Sub(t.start) - t.signal <- t - }() - - test.F(t) -} - -// An internal function but exported because it is cross-package; part of the implementation -// of go test. -func Main(matchString func(pat, str string) (bool, error), tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample) { - flag.Parse() - parseCpuList() - - before() - startAlarm() - testOk := RunTests(matchString, tests) - exampleOk := RunExamples(examples) - if !testOk || !exampleOk { - fmt.Println("FAIL") - os.Exit(1) - } - fmt.Println("PASS") - stopAlarm() - RunBenchmarks(matchString, benchmarks) - after() -} - -func (t *T) report() { - tstr := fmt.Sprintf("(%.2f seconds)", t.duration.Seconds()) - format := "--- %s: %s %s\n%s" - if t.failed { - fmt.Printf(format, "FAIL", t.name, tstr, t.output) - } else if *chatty { - fmt.Printf(format, "PASS", t.name, tstr, t.output) - } -} - -func RunTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ok bool) { - ok = true - if len(tests) == 0 { - fmt.Fprintln(os.Stderr, "testing: warning: no tests to run") - return - } - for _, procs := range cpuList { - runtime.GOMAXPROCS(procs) - // We build a new channel tree for each run of the loop. - // collector merges in one channel all the upstream signals from parallel tests. - // If all tests pump to the same channel, a bug can occur where a test - // kicks off a goroutine that Fails, yet the test still delivers a completion signal, - // which skews the counting. - var collector = make(chan any) - - numParallel := 0 - startParallel := make(chan bool) - - for i := 0; i < len(tests); i++ { - matched, err := matchString(*match, tests[i].Name) - if err != nil { - fmt.Fprintf(os.Stderr, "testing: invalid regexp for -test.run: %s\n", err) - os.Exit(1) - } - if !matched { - continue - } - testName := tests[i].Name - if procs != 1 { - testName = fmt.Sprintf("%s-%d", tests[i].Name, procs) - } - t := &T{ - common: common{ - signal: make(chan any), - }, - name: testName, - startParallel: startParallel, - } - t.self = t - if *chatty { - fmt.Printf("=== RUN %s\n", t.name) - } - go tRunner(t, &tests[i]) - out := (<-t.signal).(*T) - if out == nil { // Parallel run. - go func() { - collector <- <-t.signal - }() - numParallel++ - continue - } - t.report() - ok = ok && !out.failed - } - - running := 0 - for numParallel+running > 0 { - if running < *parallel && numParallel > 0 { - startParallel <- true - running++ - numParallel-- - continue - } - t := (<-collector).(*T) - t.report() - ok = ok && !t.failed - running-- - } - } - return -} - -// before runs before all testing. -func before() { - if *memProfileRate > 0 { - runtime.MemProfileRate = *memProfileRate - } - if *cpuProfile != "" { - f, err := os.Create(*cpuProfile) - if err != nil { - fmt.Fprintf(os.Stderr, "testing: %s", err) - return - } - if err := pprof.StartCPUProfile(f); err != nil { - fmt.Fprintf(os.Stderr, "testing: can't start cpu profile: %s", err) - f.Close() - return - } - // Could save f so after can call f.Close; not worth the effort. - } - -} - -// after runs after all testing. -func after() { - if *cpuProfile != "" { - pprof.StopCPUProfile() // flushes profile to disk - } - if *memProfile != "" { - f, err := os.Create(*memProfile) - if err != nil { - fmt.Fprintf(os.Stderr, "testing: %s", err) - return - } - if err = pprof.WriteHeapProfile(f); err != nil { - fmt.Fprintf(os.Stderr, "testing: can't write %s: %s", *memProfile, err) - } - f.Close() - } -} - -var timer *time.Timer - -// startAlarm starts an alarm if requested. -func startAlarm() { - if *timeout > 0 { - timer = time.AfterFunc(*timeout, alarm) - } -} - -// stopAlarm turns off the alarm. -func stopAlarm() { - if *timeout > 0 { - timer.Stop() - } -} - -// alarm is called if the timeout expires. -func alarm() { - panic("test timed out") -} - -func parseCpuList() { - if len(*cpuListStr) == 0 { - cpuList = append(cpuList, runtime.GOMAXPROCS(-1)) - } else { - for _, val := range strings.Split(*cpuListStr, ",") { - cpu, err := strconv.Atoi(val) - if err != nil || cpu <= 0 { - fmt.Fprintf(os.Stderr, "testing: invalid value %q for -test.cpu", val) - os.Exit(1) - } - cpuList = append(cpuList, cpu) - } - } -} diff --git a/internal/backport/go/format/benchmark_test.go b/internal/backport/go/format/benchmark_test.go deleted file mode 100644 index e2a6491d67..0000000000 --- a/internal/backport/go/format/benchmark_test.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2018 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. - -// This file provides a simple framework to add benchmarks -// based on generated input (source) files. - -package format_test - -import ( - "bytes" - "flag" - "fmt" - "golang.org/x/website/internal/backport/go/format" - "os" - "testing" -) - -var debug = flag.Bool("debug", false, "write .src files containing formatting input; for debugging") - -// array1 generates an array literal with n elements of the form: -// -// var _ = [...]byte{ -// -// // 0 -// 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, -// 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, -// 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, -// 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, -// 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, -// // 40 -// 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, -// 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, -// ... -func array1(buf *bytes.Buffer, n int) { - buf.WriteString("var _ = [...]byte{\n") - for i := 0; i < n; { - if i%10 == 0 { - fmt.Fprintf(buf, "\t// %d\n", i) - } - buf.WriteByte('\t') - for j := 0; j < 8; j++ { - fmt.Fprintf(buf, "0x%02x, ", byte(i)) - i++ - } - buf.WriteString("\n") - } - buf.WriteString("}\n") -} - -var tests = []struct { - name string - gen func(*bytes.Buffer, int) - n int -}{ - {"array1", array1, 10000}, - // add new test cases here as needed -} - -func BenchmarkFormat(b *testing.B) { - var src bytes.Buffer - for _, t := range tests { - src.Reset() - src.WriteString("package p\n") - t.gen(&src, t.n) - data := src.Bytes() - - if *debug { - filename := t.name + ".src" - err := os.WriteFile(filename, data, 0660) - if err != nil { - b.Fatalf("couldn't write %s: %v", filename, err) - } - } - - b.Run(fmt.Sprintf("%s-%d", t.name, t.n), func(b *testing.B) { - b.SetBytes(int64(len(data))) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - var err error - sink, err = format.Source(data) - if err != nil { - b.Fatal(err) - } - } - }) - } -} - -var sink []byte diff --git a/internal/backport/go/format/example_test.go b/internal/backport/go/format/example_test.go deleted file mode 100644 index 5e7ceee6d3..0000000000 --- a/internal/backport/go/format/example_test.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2018 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 format_test - -import ( - "bytes" - "fmt" - "golang.org/x/website/internal/backport/go/format" - "golang.org/x/website/internal/backport/go/parser" - "golang.org/x/website/internal/backport/go/token" - "log" -) - -func ExampleNode() { - const expr = "(6+2*3)/4" - - // parser.ParseExpr parses the argument and returns the - // corresponding ast.Node. - node, err := parser.ParseExpr(expr) - if err != nil { - log.Fatal(err) - } - - // Create a FileSet for node. Since the node does not come - // from a real source file, fset will be empty. - fset := token.NewFileSet() - - var buf bytes.Buffer - err = format.Node(&buf, fset, node) - if err != nil { - log.Fatal(err) - } - - fmt.Println(buf.String()) - - // Output: (6 + 2*3) / 4 -} diff --git a/internal/backport/go/format/format.go b/internal/backport/go/format/format.go deleted file mode 100644 index 7d1804c66b..0000000000 --- a/internal/backport/go/format/format.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2012 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 format implements standard formatting of Go source. -// -// Note that formatting of Go source code changes over time, so tools relying on -// consistent formatting should execute a specific version of the gofmt binary -// instead of using this package. That way, the formatting will be stable, and -// the tools won't need to be recompiled each time gofmt changes. -// -// For example, pre-submit checks that use this package directly would behave -// differently depending on what Go version each developer uses, causing the -// check to be inherently fragile. -package format - -import ( - "bytes" - "fmt" - "io" - - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/parser" - "golang.org/x/website/internal/backport/go/printer" - "golang.org/x/website/internal/backport/go/token" -) - -// Keep these in sync with cmd/gofmt/gofmt.go. -const ( - tabWidth = 8 - printerMode = printer.UseSpaces | printer.TabIndent | printerNormalizeNumbers - - // printerNormalizeNumbers means to canonicalize number literal prefixes - // and exponents while printing. See https://golang.org/doc/go1.13#gofmt. - // - // This value is defined in go/printer specifically for go/format and cmd/gofmt. - printerNormalizeNumbers = 1 << 30 -) - -var config = printer.Config{Mode: printerMode, Tabwidth: tabWidth} - -const parserMode = parser.ParseComments | parser.SkipObjectResolution - -// Node formats node in canonical gofmt style and writes the result to dst. -// -// The node type must be *ast.File, *printer.CommentedNode, []ast.Decl, -// []ast.Stmt, or assignment-compatible to ast.Expr, ast.Decl, ast.Spec, -// or ast.Stmt. Node does not modify node. Imports are not sorted for -// nodes representing partial source files (for instance, if the node is -// not an *ast.File or a *printer.CommentedNode not wrapping an *ast.File). -// -// The function may return early (before the entire result is written) -// and return a formatting error, for instance due to an incorrect AST. -func Node(dst io.Writer, fset *token.FileSet, node interface{}) error { - // Determine if we have a complete source file (file != nil). - var file *ast.File - var cnode *printer.CommentedNode - switch n := node.(type) { - case *ast.File: - file = n - case *printer.CommentedNode: - if f, ok := n.Node.(*ast.File); ok { - file = f - cnode = n - } - } - - // Sort imports if necessary. - if file != nil && hasUnsortedImports(file) { - // Make a copy of the AST because ast.SortImports is destructive. - // TODO(gri) Do this more efficiently. - var buf bytes.Buffer - err := config.Fprint(&buf, fset, file) - if err != nil { - return err - } - file, err = parser.ParseFile(fset, "", buf.Bytes(), parserMode) - if err != nil { - // We should never get here. If we do, provide good diagnostic. - return fmt.Errorf("format.Node internal error (%s)", err) - } - ast.SortImports(fset, file) - - // Use new file with sorted imports. - node = file - if cnode != nil { - node = &printer.CommentedNode{Node: file, Comments: cnode.Comments} - } - } - - return config.Fprint(dst, fset, node) -} - -// Source formats src in canonical gofmt style and returns the result -// or an (I/O or syntax) error. src is expected to be a syntactically -// correct Go source file, or a list of Go declarations or statements. -// -// If src is a partial source file, the leading and trailing space of src -// is applied to the result (such that it has the same leading and trailing -// space as src), and the result is indented by the same amount as the first -// line of src containing code. Imports are not sorted for partial source files. -func Source(src []byte) ([]byte, error) { - fset := token.NewFileSet() - file, sourceAdj, indentAdj, err := parse(fset, "", src, true) - if err != nil { - return nil, err - } - - if sourceAdj == nil { - // Complete source file. - // TODO(gri) consider doing this always. - ast.SortImports(fset, file) - } - - return format(fset, file, sourceAdj, indentAdj, src, config) -} - -func hasUnsortedImports(file *ast.File) bool { - for _, d := range file.Decls { - d, ok := d.(*ast.GenDecl) - if !ok || d.Tok != token.IMPORT { - // Not an import declaration, so we're done. - // Imports are always first. - return false - } - if d.Lparen.IsValid() { - // For now assume all grouped imports are unsorted. - // TODO(gri) Should check if they are sorted already. - return true - } - // Ungrouped imports are sorted by default. - } - return false -} diff --git a/internal/backport/go/format/format_test.go b/internal/backport/go/format/format_test.go deleted file mode 100644 index 59730a9872..0000000000 --- a/internal/backport/go/format/format_test.go +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright 2012 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 format - -import ( - "bytes" - "os" - "strings" - "testing" - - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/parser" - "golang.org/x/website/internal/backport/go/token" -) - -const testfile = "format_test.go" - -func diff(t *testing.T, dst, src []byte) { - line := 1 - offs := 0 // line offset - for i := 0; i < len(dst) && i < len(src); i++ { - d := dst[i] - s := src[i] - if d != s { - t.Errorf("dst:%d: %s\n", line, dst[offs:i+1]) - t.Errorf("src:%d: %s\n", line, src[offs:i+1]) - return - } - if s == '\n' { - line++ - offs = i + 1 - } - } - if len(dst) != len(src) { - t.Errorf("len(dst) = %d, len(src) = %d\nsrc = %q", len(dst), len(src), src) - } -} - -func TestNode(t *testing.T) { - src, err := os.ReadFile(testfile) - if err != nil { - t.Fatal(err) - } - - fset := token.NewFileSet() - file, err := parser.ParseFile(fset, testfile, src, parser.ParseComments) - if err != nil { - t.Fatal(err) - } - - var buf bytes.Buffer - - if err = Node(&buf, fset, file); err != nil { - t.Fatal("Node failed:", err) - } - - diff(t, buf.Bytes(), src) -} - -// Node is documented to not modify the AST. -// Test that it is so even when numbers are normalized. -func TestNodeNoModify(t *testing.T) { - const ( - src = "package p\n\nconst _ = 0000000123i\n" - golden = "package p\n\nconst _ = 123i\n" - ) - - fset := token.NewFileSet() - file, err := parser.ParseFile(fset, "", src, parser.ParseComments) - if err != nil { - t.Fatal(err) - } - - // Capture original address and value of a BasicLit node - // which will undergo formatting changes during printing. - wantLit := file.Decls[0].(*ast.GenDecl).Specs[0].(*ast.ValueSpec).Values[0].(*ast.BasicLit) - wantVal := wantLit.Value - - var buf bytes.Buffer - if err = Node(&buf, fset, file); err != nil { - t.Fatal("Node failed:", err) - } - diff(t, buf.Bytes(), []byte(golden)) - - // Check if anything changed after Node returned. - gotLit := file.Decls[0].(*ast.GenDecl).Specs[0].(*ast.ValueSpec).Values[0].(*ast.BasicLit) - gotVal := gotLit.Value - - if gotLit != wantLit { - t.Errorf("got *ast.BasicLit address %p, want %p", gotLit, wantLit) - } - if gotVal != wantVal { - t.Errorf("got *ast.BasicLit value %q, want %q", gotVal, wantVal) - } -} - -func TestSource(t *testing.T) { - src, err := os.ReadFile(testfile) - if err != nil { - t.Fatal(err) - } - - res, err := Source(src) - if err != nil { - t.Fatal("Source failed:", err) - } - - diff(t, res, src) -} - -// Test cases that are expected to fail are marked by the prefix "ERROR". -// The formatted result must look the same as the input for successful tests. -var tests = []string{ - // declaration lists - `import "golang.org/x/website/internal/backport/go/format"`, - "var x int", - "var x int\n\ntype T struct{}", - - // statement lists - "x := 0", - "f(a, b, c)\nvar x int = f(1, 2, 3)", - - // indentation, leading and trailing space - "\tx := 0\n\tgo f()", - "\tx := 0\n\tgo f()\n\n\n", - "\n\t\t\n\n\tx := 0\n\tgo f()\n\n\n", - "\n\t\t\n\n\t\t\tx := 0\n\t\t\tgo f()\n\n\n", - "\n\t\t\n\n\t\t\tx := 0\n\t\t\tconst s = `\nfoo\n`\n\n\n", // no indentation added inside raw strings - "\n\t\t\n\n\t\t\tx := 0\n\t\t\tconst s = `\n\t\tfoo\n`\n\n\n", // no indentation removed inside raw strings - - // comments - "/* Comment */", - "\t/* Comment */ ", - "\n/* Comment */ ", - "i := 5 /* Comment */", // issue #5551 - "\ta()\n//line :1", // issue #11276 - "\t//xxx\n\ta()\n//line :2", // issue #11276 - "\ta() //line :1\n\tb()\n", // issue #11276 - "x := 0\n//line :1\n//line :2", // issue #11276 - - // whitespace - "", // issue #11275 - " ", // issue #11275 - "\t", // issue #11275 - "\t\t", // issue #11275 - "\n", // issue #11275 - "\n\n", // issue #11275 - "\t\n", // issue #11275 - - // erroneous programs - "ERROR1 + 2 +", - "ERRORx := 0", - - // build comments - "// copyright\n\n//go:build x\n\npackage p\n", - "// copyright\n\n//go:build x\n// +build x\n\npackage p\n", -} - -func String(s string) (string, error) { - res, err := Source([]byte(s)) - if err != nil { - return "", err - } - return string(res), nil -} - -func TestPartial(t *testing.T) { - for _, src := range tests { - if strings.HasPrefix(src, "ERROR") { - // test expected to fail - src = src[5:] // remove ERROR prefix - res, err := String(src) - if err == nil && res == src { - t.Errorf("formatting succeeded but was expected to fail:\n%q", src) - } - } else { - // test expected to succeed - res, err := String(src) - if err != nil { - t.Errorf("formatting failed (%s):\n%q", err, src) - } else if res != src { - t.Errorf("formatting incorrect:\nsource: %q\nresult: %q", src, res) - } - } - } -} diff --git a/internal/backport/go/format/internal.go b/internal/backport/go/format/internal.go deleted file mode 100644 index c83432d799..0000000000 --- a/internal/backport/go/format/internal.go +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright 2015 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. - -// TODO(gri): This file and the file src/cmd/gofmt/internal.go are -// the same (but for this comment and the package name). Do not modify -// one without the other. Determine if we can factor out functionality -// in a public API. See also #11844 for context. - -package format - -import ( - "bytes" - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/parser" - "golang.org/x/website/internal/backport/go/printer" - "golang.org/x/website/internal/backport/go/token" - "strings" -) - -// parse parses src, which was read from the named file, -// as a Go source file, declaration, or statement list. -func parse(fset *token.FileSet, filename string, src []byte, fragmentOk bool) ( - file *ast.File, - sourceAdj func(src []byte, indent int) []byte, - indentAdj int, - err error, -) { - // Try as whole source file. - file, err = parser.ParseFile(fset, filename, src, parserMode) - // If there's no error, return. If the error is that the source file didn't begin with a - // package line and source fragments are ok, fall through to - // try as a source fragment. Stop and return on any other error. - if err == nil || !fragmentOk || !strings.Contains(err.Error(), "expected 'package'") { - return - } - - // If this is a declaration list, make it a source file - // by inserting a package clause. - // Insert using a ';', not a newline, so that the line numbers - // in psrc match the ones in src. - psrc := append([]byte("package p;"), src...) - file, err = parser.ParseFile(fset, filename, psrc, parserMode) - if err == nil { - sourceAdj = func(src []byte, indent int) []byte { - // Remove the package clause. - // Gofmt has turned the ';' into a '\n'. - src = src[indent+len("package p\n"):] - return bytes.TrimSpace(src) - } - return - } - // If the error is that the source file didn't begin with a - // declaration, fall through to try as a statement list. - // Stop and return on any other error. - if !strings.Contains(err.Error(), "expected declaration") { - return - } - - // If this is a statement list, make it a source file - // by inserting a package clause and turning the list - // into a function body. This handles expressions too. - // Insert using a ';', not a newline, so that the line numbers - // in fsrc match the ones in src. Add an extra '\n' before the '}' - // to make sure comments are flushed before the '}'. - fsrc := append(append([]byte("package p; func _() {"), src...), '\n', '\n', '}') - file, err = parser.ParseFile(fset, filename, fsrc, parserMode) - if err == nil { - sourceAdj = func(src []byte, indent int) []byte { - // Cap adjusted indent to zero. - if indent < 0 { - indent = 0 - } - // Remove the wrapping. - // Gofmt has turned the "; " into a "\n\n". - // There will be two non-blank lines with indent, hence 2*indent. - src = src[2*indent+len("package p\n\nfunc _() {"):] - // Remove only the "}\n" suffix: remaining whitespaces will be trimmed anyway - src = src[:len(src)-len("}\n")] - return bytes.TrimSpace(src) - } - // Gofmt has also indented the function body one level. - // Adjust that with indentAdj. - indentAdj = -1 - } - - // Succeeded, or out of options. - return -} - -// format formats the given package file originally obtained from src -// and adjusts the result based on the original source via sourceAdj -// and indentAdj. -func format( - fset *token.FileSet, - file *ast.File, - sourceAdj func(src []byte, indent int) []byte, - indentAdj int, - src []byte, - cfg printer.Config, -) ([]byte, error) { - if sourceAdj == nil { - // Complete source file. - var buf bytes.Buffer - err := cfg.Fprint(&buf, fset, file) - if err != nil { - return nil, err - } - return buf.Bytes(), nil - } - - // Partial source file. - // Determine and prepend leading space. - i, j := 0, 0 - for j < len(src) && isSpace(src[j]) { - if src[j] == '\n' { - i = j + 1 // byte offset of last line in leading space - } - j++ - } - var res []byte - res = append(res, src[:i]...) - - // Determine and prepend indentation of first code line. - // Spaces are ignored unless there are no tabs, - // in which case spaces count as one tab. - indent := 0 - hasSpace := false - for _, b := range src[i:j] { - switch b { - case ' ': - hasSpace = true - case '\t': - indent++ - } - } - if indent == 0 && hasSpace { - indent = 1 - } - for i := 0; i < indent; i++ { - res = append(res, '\t') - } - - // Format the source. - // Write it without any leading and trailing space. - cfg.Indent = indent + indentAdj - var buf bytes.Buffer - err := cfg.Fprint(&buf, fset, file) - if err != nil { - return nil, err - } - out := sourceAdj(buf.Bytes(), cfg.Indent) - - // If the adjusted output is empty, the source - // was empty but (possibly) for white space. - // The result is the incoming source. - if len(out) == 0 { - return src, nil - } - - // Otherwise, append output to leading space. - res = append(res, out...) - - // Determine and append trailing space. - i = len(src) - for i > 0 && isSpace(src[i-1]) { - i-- - } - return append(res, src[i:]...), nil -} - -// isSpace reports whether the byte is a space character. -// isSpace defines a space as being among the following bytes: ' ', '\t', '\n' and '\r'. -func isSpace(b byte) bool { - return b == ' ' || b == '\t' || b == '\n' || b == '\r' -} diff --git a/internal/backport/go/internal/typeparams/common.go b/internal/backport/go/internal/typeparams/common.go deleted file mode 100644 index 9b82e6061a..0000000000 --- a/internal/backport/go/internal/typeparams/common.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2021 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 typeparams provides functions to work with type parameter data -// stored in the AST, while these AST changes are guarded by a build -// constraint. -package typeparams - -// 'Hidden' parser modes to control the parsing of type-parameter related -// features. -const ( - DisallowTypeSets = 1 << 29 // Disallow eliding 'interface' in constraint type sets. - DisallowParsing = 1 << 30 // Disallow type parameters entirely. -) diff --git a/internal/backport/go/internal/typeparams/typeparams.go b/internal/backport/go/internal/typeparams/typeparams.go deleted file mode 100644 index 2f7a5ce210..0000000000 --- a/internal/backport/go/internal/typeparams/typeparams.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2021 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 typeparams - -import ( - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/token" -) - -func PackIndexExpr(x ast.Expr, lbrack token.Pos, exprs []ast.Expr, rbrack token.Pos) ast.Expr { - switch len(exprs) { - case 0: - panic("internal error: PackIndexExpr with empty expr slice") - case 1: - return &ast.IndexExpr{ - X: x, - Lbrack: lbrack, - Index: exprs[0], - Rbrack: rbrack, - } - default: - return &ast.IndexListExpr{ - X: x, - Lbrack: lbrack, - Indices: exprs, - Rbrack: rbrack, - } - } -} - -// IndexExpr wraps an ast.IndexExpr or ast.IndexListExpr. -// -// Orig holds the original ast.Expr from which this IndexExpr was derived. -type IndexExpr struct { - Orig ast.Expr // the wrapped expr, which may be distinct from the IndexListExpr below. - *ast.IndexListExpr -} - -func UnpackIndexExpr(n ast.Node) *IndexExpr { - switch e := n.(type) { - case *ast.IndexExpr: - return &IndexExpr{e, &ast.IndexListExpr{ - X: e.X, - Lbrack: e.Lbrack, - Indices: []ast.Expr{e.Index}, - Rbrack: e.Rbrack, - }} - case *ast.IndexListExpr: - return &IndexExpr{e, e} - } - return nil -} diff --git a/internal/backport/go/parser/error_test.go b/internal/backport/go/parser/error_test.go deleted file mode 100644 index 38658c2434..0000000000 --- a/internal/backport/go/parser/error_test.go +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright 2012 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. - -// This file implements a parser test harness. The files in the testdata -// directory are parsed and the errors reported are compared against the -// error messages expected in the test files. The test files must end in -// .src rather than .go so that they are not disturbed by gofmt runs. -// -// Expected errors are indicated in the test files by putting a comment -// of the form /* ERROR "rx" */ immediately following an offending token. -// The harness will verify that an error matching the regular expression -// rx is reported at that source position. -// -// For instance, the following test file indicates that a "not declared" -// error should be reported for the undeclared variable x: -// -// package p -// func f() { -// _ = x /* ERROR "not declared" */ + 1 -// } - -package parser - -import ( - "flag" - "golang.org/x/website/internal/backport/go/internal/typeparams" - "golang.org/x/website/internal/backport/go/scanner" - "golang.org/x/website/internal/backport/go/token" - "os" - "path/filepath" - "regexp" - "strings" - "testing" -) - -var traceErrs = flag.Bool("trace_errs", false, "whether to enable tracing for error tests") - -const testdata = "testdata" - -// getFile assumes that each filename occurs at most once -func getFile(fset *token.FileSet, filename string) (file *token.File) { - fset.Iterate(func(f *token.File) bool { - if f.Name() == filename { - if file != nil { - panic(filename + " used multiple times") - } - file = f - } - return true - }) - return file -} - -func getPos(fset *token.FileSet, filename string, offset int) token.Pos { - if f := getFile(fset, filename); f != nil { - return f.Pos(offset) - } - return token.NoPos -} - -// ERROR comments must be of the form /* ERROR "rx" */ and rx is -// a regular expression that matches the expected error message. -// The special form /* ERROR HERE "rx" */ must be used for error -// messages that appear immediately after a token, rather than at -// a token's position. -var errRx = regexp.MustCompile(`^/\* *ERROR *(HERE)? *"([^"]*)" *\*/$`) - -// expectedErrors collects the regular expressions of ERROR comments found -// in files and returns them as a map of error positions to error messages. -func expectedErrors(fset *token.FileSet, filename string, src []byte) map[token.Pos]string { - errors := make(map[token.Pos]string) - - var s scanner.Scanner - // file was parsed already - do not add it again to the file - // set otherwise the position information returned here will - // not match the position information collected by the parser - s.Init(getFile(fset, filename), src, nil, scanner.ScanComments) - var prev token.Pos // position of last non-comment, non-semicolon token - var here token.Pos // position immediately after the token at position prev - - for { - pos, tok, lit := s.Scan() - switch tok { - case token.EOF: - return errors - case token.COMMENT: - s := errRx.FindStringSubmatch(lit) - if len(s) == 3 { - pos := prev - if s[1] == "HERE" { - pos = here - } - errors[pos] = s[2] - } - case token.SEMICOLON: - // don't use the position of auto-inserted (invisible) semicolons - if lit != ";" { - break - } - fallthrough - default: - prev = pos - var l int // token length - if tok.IsLiteral() { - l = len(lit) - } else { - l = len(tok.String()) - } - here = prev + token.Pos(l) - } - } -} - -// compareErrors compares the map of expected error messages with the list -// of found errors and reports discrepancies. -func compareErrors(t *testing.T, fset *token.FileSet, expected map[token.Pos]string, found scanner.ErrorList) { - t.Helper() - for _, error := range found { - // error.Pos is a token.Position, but we want - // a token.Pos so we can do a map lookup - pos := getPos(fset, error.Pos.Filename, error.Pos.Offset) - if msg, found := expected[pos]; found { - // we expect a message at pos; check if it matches - rx, err := regexp.Compile(msg) - if err != nil { - t.Errorf("%s: %v", error.Pos, err) - continue - } - if match := rx.MatchString(error.Msg); !match { - t.Errorf("%s: %q does not match %q", error.Pos, error.Msg, msg) - continue - } - // we have a match - eliminate this error - delete(expected, pos) - } else { - // To keep in mind when analyzing failed test output: - // If the same error position occurs multiple times in errors, - // this message will be triggered (because the first error at - // the position removes this position from the expected errors). - t.Errorf("%s: unexpected error: %s", error.Pos, error.Msg) - } - } - - // there should be no expected errors left - if len(expected) > 0 { - t.Errorf("%d errors not reported:", len(expected)) - for pos, msg := range expected { - t.Errorf("%s: %s\n", fset.Position(pos), msg) - } - } -} - -func checkErrors(t *testing.T, filename string, input interface{}, mode Mode, expectErrors bool) { - t.Helper() - src, err := readSource(filename, input) - if err != nil { - t.Error(err) - return - } - - fset := token.NewFileSet() - _, err = ParseFile(fset, filename, src, mode) - found, ok := err.(scanner.ErrorList) - if err != nil && !ok { - t.Error(err) - return - } - found.RemoveMultiples() - - expected := map[token.Pos]string{} - if expectErrors { - // we are expecting the following errors - // (collect these after parsing a file so that it is found in the file set) - expected = expectedErrors(fset, filename, src) - } - - // verify errors returned by the parser - compareErrors(t, fset, expected, found) -} - -func TestErrors(t *testing.T) { - list, err := os.ReadDir(testdata) - if err != nil { - t.Fatal(err) - } - for _, d := range list { - name := d.Name() - t.Run(name, func(t *testing.T) { - if !d.IsDir() && !strings.HasPrefix(name, ".") && (strings.HasSuffix(name, ".src") || strings.HasSuffix(name, ".go2")) { - mode := DeclarationErrors | AllErrors - if !strings.HasSuffix(name, ".go2") { - mode |= typeparams.DisallowParsing - } - if *traceErrs { - mode |= Trace - } - checkErrors(t, filepath.Join(testdata, name), nil, mode, true) - } - }) - } -} diff --git a/internal/backport/go/parser/example_test.go b/internal/backport/go/parser/example_test.go deleted file mode 100644 index 35fdd33500..0000000000 --- a/internal/backport/go/parser/example_test.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2012 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 parser_test - -import ( - "fmt" - "golang.org/x/website/internal/backport/go/parser" - "golang.org/x/website/internal/backport/go/token" -) - -func ExampleParseFile() { - fset := token.NewFileSet() // positions are relative to fset - - src := `package foo - -import ( - "fmt" - "time" -) - -func bar() { - fmt.Println(time.Now()) -}` - - // Parse src but stop after processing the imports. - f, err := parser.ParseFile(fset, "", src, parser.ImportsOnly) - if err != nil { - fmt.Println(err) - return - } - - // Print the imports from the file's AST. - for _, s := range f.Imports { - fmt.Println(s.Path.Value) - } - - // output: - // - // "fmt" - // "time" -} diff --git a/internal/backport/go/parser/interface.go b/internal/backport/go/parser/interface.go deleted file mode 100644 index e253539e52..0000000000 --- a/internal/backport/go/parser/interface.go +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright 2009 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. - -// This file contains the exported entry points for invoking the parser. - -package parser - -import ( - "bytes" - "errors" - "io" - "io/fs" - "os" - "path/filepath" - "strings" - - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/token" -) - -// If src != nil, readSource converts src to a []byte if possible; -// otherwise it returns an error. If src == nil, readSource returns -// the result of reading the file specified by filename. -func readSource(filename string, src interface{}) ([]byte, error) { - if src != nil { - switch s := src.(type) { - case string: - return []byte(s), nil - case []byte: - return s, nil - case *bytes.Buffer: - // is io.Reader, but src is already available in []byte form - if s != nil { - return s.Bytes(), nil - } - case io.Reader: - return io.ReadAll(s) - } - return nil, errors.New("invalid source") - } - return os.ReadFile(filename) -} - -// A Mode value is a set of flags (or 0). -// They control the amount of source code parsed and other optional -// parser functionality. -type Mode uint - -const ( - PackageClauseOnly Mode = 1 << iota // stop parsing after package clause - ImportsOnly // stop parsing after import declarations - ParseComments // parse comments and add them to AST - Trace // print a trace of parsed productions - DeclarationErrors // report declaration errors - SpuriousErrors // same as AllErrors, for backward-compatibility - SkipObjectResolution // don't resolve identifiers to objects - see ParseFile - AllErrors = SpuriousErrors // report all errors (not just the first 10 on different lines) -) - -// ParseFile parses the source code of a single Go source file and returns -// the corresponding ast.File node. The source code may be provided via -// the filename of the source file, or via the src parameter. -// -// If src != nil, ParseFile parses the source from src and the filename is -// only used when recording position information. The type of the argument -// for the src parameter must be string, []byte, or io.Reader. -// If src == nil, ParseFile parses the file specified by filename. -// -// The mode parameter controls the amount of source text parsed and other -// optional parser functionality. If the SkipObjectResolution mode bit is set, -// the object resolution phase of parsing will be skipped, causing File.Scope, -// File.Unresolved, and all Ident.Obj fields to be nil. -// -// Position information is recorded in the file set fset, which must not be -// nil. -// -// If the source couldn't be read, the returned AST is nil and the error -// indicates the specific failure. If the source was read but syntax -// errors were found, the result is a partial AST (with ast.Bad* nodes -// representing the fragments of erroneous source code). Multiple errors -// are returned via a scanner.ErrorList which is sorted by source position. -func ParseFile(fset *token.FileSet, filename string, src interface{}, mode Mode) (f *ast.File, err error) { - if fset == nil { - panic("parser.ParseFile: no token.FileSet provided (fset == nil)") - } - - // get source - text, err := readSource(filename, src) - if err != nil { - return nil, err - } - - var p parser - defer func() { - if e := recover(); e != nil { - // resume same panic if it's not a bailout - bail, ok := e.(bailout) - if !ok { - panic(e) - } else if bail.msg != "" { - p.errors.Add(p.file.Position(bail.pos), bail.msg) - } - } - - // set result values - if f == nil { - // source is not a valid Go source file - satisfy - // ParseFile API and return a valid (but) empty - // *ast.File - f = &ast.File{ - Name: new(ast.Ident), - Scope: ast.NewScope(nil), - } - } - - p.errors.Sort() - err = p.errors.Err() - }() - - // parse source - p.init(fset, filename, text, mode) - f = p.parseFile() - - return -} - -// ParseDir calls ParseFile for all files with names ending in ".go" in the -// directory specified by path and returns a map of package name -> package -// AST with all the packages found. -// -// If filter != nil, only the files with fs.FileInfo entries passing through -// the filter (and ending in ".go") are considered. The mode bits are passed -// to ParseFile unchanged. Position information is recorded in fset, which -// must not be nil. -// -// If the directory couldn't be read, a nil map and the respective error are -// returned. If a parse error occurred, a non-nil but incomplete map and the -// first error encountered are returned. -func ParseDir(fset *token.FileSet, path string, filter func(fs.FileInfo) bool, mode Mode) (pkgs map[string]*ast.Package, first error) { - list, err := os.ReadDir(path) - if err != nil { - return nil, err - } - - pkgs = make(map[string]*ast.Package) - for _, d := range list { - if d.IsDir() || !strings.HasSuffix(d.Name(), ".go") { - continue - } - if filter != nil { - info, err := d.Info() - if err != nil { - return nil, err - } - if !filter(info) { - continue - } - } - filename := filepath.Join(path, d.Name()) - if src, err := ParseFile(fset, filename, nil, mode); err == nil { - name := src.Name.Name - pkg, found := pkgs[name] - if !found { - pkg = &ast.Package{ - Name: name, - Files: make(map[string]*ast.File), - } - pkgs[name] = pkg - } - pkg.Files[filename] = src - } else if first == nil { - first = err - } - } - - return -} - -// ParseExprFrom is a convenience function for parsing an expression. -// The arguments have the same meaning as for ParseFile, but the source must -// be a valid Go (type or value) expression. Specifically, fset must not -// be nil. -// -// If the source couldn't be read, the returned AST is nil and the error -// indicates the specific failure. If the source was read but syntax -// errors were found, the result is a partial AST (with ast.Bad* nodes -// representing the fragments of erroneous source code). Multiple errors -// are returned via a scanner.ErrorList which is sorted by source position. -func ParseExprFrom(fset *token.FileSet, filename string, src interface{}, mode Mode) (expr ast.Expr, err error) { - if fset == nil { - panic("parser.ParseExprFrom: no token.FileSet provided (fset == nil)") - } - - // get source - text, err := readSource(filename, src) - if err != nil { - return nil, err - } - - var p parser - defer func() { - if e := recover(); e != nil { - // resume same panic if it's not a bailout - bail, ok := e.(bailout) - if !ok { - panic(e) - } else if bail.msg != "" { - p.errors.Add(p.file.Position(bail.pos), bail.msg) - } - } - p.errors.Sort() - err = p.errors.Err() - }() - - // parse expr - p.init(fset, filename, text, mode) - expr = p.parseRhsOrType() - - // If a semicolon was inserted, consume it; - // report an error if there's more tokens. - if p.tok == token.SEMICOLON && p.lit == "\n" { - p.next() - } - p.expect(token.EOF) - - return -} - -// ParseExpr is a convenience function for obtaining the AST of an expression x. -// The position information recorded in the AST is undefined. The filename used -// in error messages is the empty string. -// -// If syntax errors were found, the result is a partial AST (with ast.Bad* nodes -// representing the fragments of erroneous source code). Multiple errors are -// returned via a scanner.ErrorList which is sorted by source position. -func ParseExpr(x string) (ast.Expr, error) { - return ParseExprFrom(token.NewFileSet(), "", []byte(x), 0) -} diff --git a/internal/backport/go/parser/parser.go b/internal/backport/go/parser/parser.go deleted file mode 100644 index ea33adfc88..0000000000 --- a/internal/backport/go/parser/parser.go +++ /dev/null @@ -1,2936 +0,0 @@ -// Copyright 2009 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 parser implements a parser for Go source files. Input may be -// provided in a variety of forms (see the various Parse* functions); the -// output is an abstract syntax tree (AST) representing the Go source. The -// parser is invoked through one of the Parse* functions. -// -// The parser accepts a larger language than is syntactically permitted by -// the Go spec, for simplicity, and for improved robustness in the presence -// of syntax errors. For instance, in method declarations, the receiver is -// treated like an ordinary parameter list and thus may contain multiple -// entries where the spec permits exactly one. Consequently, the corresponding -// field in the AST (ast.FuncDecl.Recv) field is not restricted to one entry. -package parser - -import ( - "fmt" - "strconv" - "strings" - "unicode" - - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/internal/typeparams" - "golang.org/x/website/internal/backport/go/scanner" - "golang.org/x/website/internal/backport/go/token" -) - -// The parser structure holds the parser's internal state. -type parser struct { - file *token.File - errors scanner.ErrorList - scanner scanner.Scanner - - // Tracing/debugging - mode Mode // parsing mode - trace bool // == (mode&Trace != 0) - indent int // indentation used for tracing output - - // Comments - comments []*ast.CommentGroup - leadComment *ast.CommentGroup // last lead comment - lineComment *ast.CommentGroup // last line comment - - // Next token - pos token.Pos // token position - tok token.Token // one token look-ahead - lit string // token literal - - // Error recovery - // (used to limit the number of calls to parser.advance - // w/o making scanning progress - avoids potential endless - // loops across multiple parser functions during error recovery) - syncPos token.Pos // last synchronization position - syncCnt int // number of parser.advance calls without progress - - // Non-syntactic parser control - exprLev int // < 0: in control clause, >= 0: in expression - inRhs bool // if set, the parser is parsing a rhs expression - - imports []*ast.ImportSpec // list of imports - - // nestLev is used to track and limit the recursion depth - // during parsing. - nestLev int -} - -func (p *parser) init(fset *token.FileSet, filename string, src []byte, mode Mode) { - p.file = fset.AddFile(filename, -1, len(src)) - var m scanner.Mode - if mode&ParseComments != 0 { - m = scanner.ScanComments - } - eh := func(pos token.Position, msg string) { p.errors.Add(pos, msg) } - p.scanner.Init(p.file, src, eh, m) - - p.mode = mode - p.trace = mode&Trace != 0 // for convenience (p.trace is used frequently) - p.next() -} - -func (p *parser) allowGenerics() bool { return p.mode&typeparams.DisallowParsing == 0 } -func (p *parser) allowTypeSets() bool { return p.mode&typeparams.DisallowTypeSets == 0 } - -// ---------------------------------------------------------------------------- -// Parsing support - -func (p *parser) printTrace(a ...interface{}) { - const dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . " - const n = len(dots) - pos := p.file.Position(p.pos) - fmt.Printf("%5d:%3d: ", pos.Line, pos.Column) - i := 2 * p.indent - for i > n { - fmt.Print(dots) - i -= n - } - // i <= n - fmt.Print(dots[0:i]) - fmt.Println(a...) -} - -func trace(p *parser, msg string) *parser { - p.printTrace(msg, "(") - p.indent++ - return p -} - -// Usage pattern: defer un(trace(p, "...")) -func un(p *parser) { - p.indent-- - p.printTrace(")") -} - -// maxNestLev is the deepest we're willing to recurse during parsing -const maxNestLev int = 1e5 - -func incNestLev(p *parser) *parser { - p.nestLev++ - if p.nestLev > maxNestLev { - p.error(p.pos, "exceeded max nesting depth") - panic(bailout{}) - } - return p -} - -// decNestLev is used to track nesting depth during parsing to prevent stack exhaustion. -// It is used along with incNestLev in a similar fashion to how un and trace are used. -func decNestLev(p *parser) { - p.nestLev-- -} - -// Advance to the next token. -func (p *parser) next0() { - // Because of one-token look-ahead, print the previous token - // when tracing as it provides a more readable output. The - // very first token (!p.pos.IsValid()) is not initialized - // (it is token.ILLEGAL), so don't print it. - if p.trace && p.pos.IsValid() { - s := p.tok.String() - switch { - case p.tok.IsLiteral(): - p.printTrace(s, p.lit) - case p.tok.IsOperator(), p.tok.IsKeyword(): - p.printTrace("\"" + s + "\"") - default: - p.printTrace(s) - } - } - - p.pos, p.tok, p.lit = p.scanner.Scan() -} - -// Consume a comment and return it and the line on which it ends. -func (p *parser) consumeComment() (comment *ast.Comment, endline int) { - // /*-style comments may end on a different line than where they start. - // Scan the comment for '\n' chars and adjust endline accordingly. - endline = p.file.Line(p.pos) - if p.lit[1] == '*' { - // don't use range here - no need to decode Unicode code points - for i := 0; i < len(p.lit); i++ { - if p.lit[i] == '\n' { - endline++ - } - } - } - - comment = &ast.Comment{Slash: p.pos, Text: p.lit} - p.next0() - - return -} - -// Consume a group of adjacent comments, add it to the parser's -// comments list, and return it together with the line at which -// the last comment in the group ends. A non-comment token or n -// empty lines terminate a comment group. -func (p *parser) consumeCommentGroup(n int) (comments *ast.CommentGroup, endline int) { - var list []*ast.Comment - endline = p.file.Line(p.pos) - for p.tok == token.COMMENT && p.file.Line(p.pos) <= endline+n { - var comment *ast.Comment - comment, endline = p.consumeComment() - list = append(list, comment) - } - - // add comment group to the comments list - comments = &ast.CommentGroup{List: list} - p.comments = append(p.comments, comments) - - return -} - -// Advance to the next non-comment token. In the process, collect -// any comment groups encountered, and remember the last lead and -// line comments. -// -// A lead comment is a comment group that starts and ends in a -// line without any other tokens and that is followed by a non-comment -// token on the line immediately after the comment group. -// -// A line comment is a comment group that follows a non-comment -// token on the same line, and that has no tokens after it on the line -// where it ends. -// -// Lead and line comments may be considered documentation that is -// stored in the AST. -func (p *parser) next() { - p.leadComment = nil - p.lineComment = nil - prev := p.pos - p.next0() - - if p.tok == token.COMMENT { - var comment *ast.CommentGroup - var endline int - - if p.file.Line(p.pos) == p.file.Line(prev) { - // The comment is on same line as the previous token; it - // cannot be a lead comment but may be a line comment. - comment, endline = p.consumeCommentGroup(0) - if p.file.Line(p.pos) != endline || p.tok == token.EOF { - // The next token is on a different line, thus - // the last comment group is a line comment. - p.lineComment = comment - } - } - - // consume successor comments, if any - endline = -1 - for p.tok == token.COMMENT { - comment, endline = p.consumeCommentGroup(1) - } - - if endline+1 == p.file.Line(p.pos) { - // The next token is following on the line immediately after the - // comment group, thus the last comment group is a lead comment. - p.leadComment = comment - } - } -} - -// A bailout panic is raised to indicate early termination. pos and msg are -// only populated when bailing out of object resolution. -type bailout struct { - pos token.Pos - msg string -} - -func (p *parser) error(pos token.Pos, msg string) { - if p.trace { - defer un(trace(p, "error: "+msg)) - } - - epos := p.file.Position(pos) - - // If AllErrors is not set, discard errors reported on the same line - // as the last recorded error and stop parsing if there are more than - // 10 errors. - if p.mode&AllErrors == 0 { - n := len(p.errors) - if n > 0 && p.errors[n-1].Pos.Line == epos.Line { - return // discard - likely a spurious error - } - if n > 10 { - panic(bailout{}) - } - } - - p.errors.Add(epos, msg) -} - -func (p *parser) errorExpected(pos token.Pos, msg string) { - msg = "expected " + msg - if pos == p.pos { - // the error happened at the current position; - // make the error message more specific - switch { - case p.tok == token.SEMICOLON && p.lit == "\n": - msg += ", found newline" - case p.tok.IsLiteral(): - // print 123 rather than 'INT', etc. - msg += ", found " + p.lit - default: - msg += ", found '" + p.tok.String() + "'" - } - } - p.error(pos, msg) -} - -func (p *parser) expect(tok token.Token) token.Pos { - pos := p.pos - if p.tok != tok { - p.errorExpected(pos, "'"+tok.String()+"'") - } - p.next() // make progress - return pos -} - -// expect2 is like expect, but it returns an invalid position -// if the expected token is not found. -func (p *parser) expect2(tok token.Token) (pos token.Pos) { - if p.tok == tok { - pos = p.pos - } else { - p.errorExpected(p.pos, "'"+tok.String()+"'") - } - p.next() // make progress - return -} - -// expectClosing is like expect but provides a better error message -// for the common case of a missing comma before a newline. -func (p *parser) expectClosing(tok token.Token, context string) token.Pos { - if p.tok != tok && p.tok == token.SEMICOLON && p.lit == "\n" { - p.error(p.pos, "missing ',' before newline in "+context) - p.next() - } - return p.expect(tok) -} - -func (p *parser) expectSemi() { - // semicolon is optional before a closing ')' or '}' - if p.tok != token.RPAREN && p.tok != token.RBRACE { - switch p.tok { - case token.COMMA: - // permit a ',' instead of a ';' but complain - p.errorExpected(p.pos, "';'") - fallthrough - case token.SEMICOLON: - p.next() - default: - p.errorExpected(p.pos, "';'") - p.advance(stmtStart) - } - } -} - -func (p *parser) atComma(context string, follow token.Token) bool { - if p.tok == token.COMMA { - return true - } - if p.tok != follow { - msg := "missing ','" - if p.tok == token.SEMICOLON && p.lit == "\n" { - msg += " before newline" - } - p.error(p.pos, msg+" in "+context) - return true // "insert" comma and continue - } - return false -} - -func assert(cond bool, msg string) { - if !cond { - panic("go/parser internal error: " + msg) - } -} - -// advance consumes tokens until the current token p.tok -// is in the 'to' set, or token.EOF. For error recovery. -func (p *parser) advance(to map[token.Token]bool) { - for ; p.tok != token.EOF; p.next() { - if to[p.tok] { - // Return only if parser made some progress since last - // sync or if it has not reached 10 advance calls without - // progress. Otherwise consume at least one token to - // avoid an endless parser loop (it is possible that - // both parseOperand and parseStmt call advance and - // correctly do not advance, thus the need for the - // invocation limit p.syncCnt). - if p.pos == p.syncPos && p.syncCnt < 10 { - p.syncCnt++ - return - } - if p.pos > p.syncPos { - p.syncPos = p.pos - p.syncCnt = 0 - return - } - // Reaching here indicates a parser bug, likely an - // incorrect token list in this function, but it only - // leads to skipping of possibly correct code if a - // previous error is present, and thus is preferred - // over a non-terminating parse. - } - } -} - -var stmtStart = map[token.Token]bool{ - token.BREAK: true, - token.CONST: true, - token.CONTINUE: true, - token.DEFER: true, - token.FALLTHROUGH: true, - token.FOR: true, - token.GO: true, - token.GOTO: true, - token.IF: true, - token.RETURN: true, - token.SELECT: true, - token.SWITCH: true, - token.TYPE: true, - token.VAR: true, -} - -var declStart = map[token.Token]bool{ - token.CONST: true, - token.TYPE: true, - token.VAR: true, -} - -var exprEnd = map[token.Token]bool{ - token.COMMA: true, - token.COLON: true, - token.SEMICOLON: true, - token.RPAREN: true, - token.RBRACK: true, - token.RBRACE: true, -} - -// safePos returns a valid file position for a given position: If pos -// is valid to begin with, safePos returns pos. If pos is out-of-range, -// safePos returns the EOF position. -// -// This is hack to work around "artificial" end positions in the AST which -// are computed by adding 1 to (presumably valid) token positions. If the -// token positions are invalid due to parse errors, the resulting end position -// may be past the file's EOF position, which would lead to panics if used -// later on. -func (p *parser) safePos(pos token.Pos) (res token.Pos) { - defer func() { - if recover() != nil { - res = token.Pos(p.file.Base() + p.file.Size()) // EOF position - } - }() - _ = p.file.Offset(pos) // trigger a panic if position is out-of-range - return pos -} - -// ---------------------------------------------------------------------------- -// Identifiers - -func (p *parser) parseIdent() *ast.Ident { - pos := p.pos - name := "_" - if p.tok == token.IDENT { - name = p.lit - p.next() - } else { - p.expect(token.IDENT) // use expect() error handling - } - return &ast.Ident{NamePos: pos, Name: name} -} - -func (p *parser) parseIdentList() (list []*ast.Ident) { - if p.trace { - defer un(trace(p, "IdentList")) - } - - list = append(list, p.parseIdent()) - for p.tok == token.COMMA { - p.next() - list = append(list, p.parseIdent()) - } - - return -} - -// ---------------------------------------------------------------------------- -// Common productions - -// If lhs is set, result list elements which are identifiers are not resolved. -func (p *parser) parseExprList() (list []ast.Expr) { - if p.trace { - defer un(trace(p, "ExpressionList")) - } - - list = append(list, p.checkExpr(p.parseExpr())) - for p.tok == token.COMMA { - p.next() - list = append(list, p.checkExpr(p.parseExpr())) - } - - return -} - -func (p *parser) parseList(inRhs bool) []ast.Expr { - old := p.inRhs - p.inRhs = inRhs - list := p.parseExprList() - p.inRhs = old - return list -} - -// ---------------------------------------------------------------------------- -// Types - -func (p *parser) parseType() ast.Expr { - if p.trace { - defer un(trace(p, "Type")) - } - - typ := p.tryIdentOrType() - - if typ == nil { - pos := p.pos - p.errorExpected(pos, "type") - p.advance(exprEnd) - return &ast.BadExpr{From: pos, To: p.pos} - } - - return typ -} - -func (p *parser) parseQualifiedIdent(ident *ast.Ident) ast.Expr { - if p.trace { - defer un(trace(p, "QualifiedIdent")) - } - - typ := p.parseTypeName(ident) - if p.tok == token.LBRACK && p.allowGenerics() { - typ = p.parseTypeInstance(typ) - } - - return typ -} - -// If the result is an identifier, it is not resolved. -func (p *parser) parseTypeName(ident *ast.Ident) ast.Expr { - if p.trace { - defer un(trace(p, "TypeName")) - } - - if ident == nil { - ident = p.parseIdent() - } - - if p.tok == token.PERIOD { - // ident is a package name - p.next() - sel := p.parseIdent() - return &ast.SelectorExpr{X: ident, Sel: sel} - } - - return ident -} - -// "[" has already been consumed, and lbrack is its position. -// If len != nil it is the already consumed array length. -func (p *parser) parseArrayType(lbrack token.Pos, len ast.Expr) *ast.ArrayType { - if p.trace { - defer un(trace(p, "ArrayType")) - } - - if len == nil { - p.exprLev++ - // always permit ellipsis for more fault-tolerant parsing - if p.tok == token.ELLIPSIS { - len = &ast.Ellipsis{Ellipsis: p.pos} - p.next() - } else if p.tok != token.RBRACK { - len = p.parseRhs() - } - p.exprLev-- - } - if p.tok == token.COMMA { - // Trailing commas are accepted in type parameter - // lists but not in array type declarations. - // Accept for better error handling but complain. - p.error(p.pos, "unexpected comma; expecting ]") - p.next() - } - p.expect(token.RBRACK) - elt := p.parseType() - return &ast.ArrayType{Lbrack: lbrack, Len: len, Elt: elt} -} - -func (p *parser) parseArrayFieldOrTypeInstance(x *ast.Ident) (*ast.Ident, ast.Expr) { - if p.trace { - defer un(trace(p, "ArrayFieldOrTypeInstance")) - } - - // TODO(gri) Should we allow a trailing comma in a type argument - // list such as T[P,]? (We do in parseTypeInstance). - lbrack := p.expect(token.LBRACK) - var args []ast.Expr - var firstComma token.Pos - // TODO(rfindley): consider changing parseRhsOrType so that this function variable - // is not needed. - argparser := p.parseRhsOrType - if !p.allowGenerics() { - argparser = p.parseRhs - } - if p.tok != token.RBRACK { - p.exprLev++ - args = append(args, argparser()) - for p.tok == token.COMMA { - if !firstComma.IsValid() { - firstComma = p.pos - } - p.next() - args = append(args, argparser()) - } - p.exprLev-- - } - rbrack := p.expect(token.RBRACK) - - if len(args) == 0 { - // x []E - elt := p.parseType() - return x, &ast.ArrayType{Lbrack: lbrack, Elt: elt} - } - - // x [P]E or x[P] - if len(args) == 1 { - elt := p.tryIdentOrType() - if elt != nil { - // x [P]E - return x, &ast.ArrayType{Lbrack: lbrack, Len: args[0], Elt: elt} - } - if !p.allowGenerics() { - p.error(rbrack, "missing element type in array type expression") - return nil, &ast.BadExpr{From: args[0].Pos(), To: args[0].End()} - } - } - - if !p.allowGenerics() { - p.error(firstComma, "expected ']', found ','") - return x, &ast.BadExpr{From: args[0].Pos(), To: args[len(args)-1].End()} - } - - // x[P], x[P1, P2], ... - return nil, typeparams.PackIndexExpr(x, lbrack, args, rbrack) -} - -func (p *parser) parseFieldDecl() *ast.Field { - if p.trace { - defer un(trace(p, "FieldDecl")) - } - - doc := p.leadComment - - var names []*ast.Ident - var typ ast.Expr - if p.tok == token.IDENT { - name := p.parseIdent() - if p.tok == token.PERIOD || p.tok == token.STRING || p.tok == token.SEMICOLON || p.tok == token.RBRACE { - // embedded type - typ = name - if p.tok == token.PERIOD { - typ = p.parseQualifiedIdent(name) - } - } else { - // name1, name2, ... T - names = []*ast.Ident{name} - for p.tok == token.COMMA { - p.next() - names = append(names, p.parseIdent()) - } - // Careful dance: We don't know if we have an embedded instantiated - // type T[P1, P2, ...] or a field T of array type []E or [P]E. - if len(names) == 1 && p.tok == token.LBRACK { - name, typ = p.parseArrayFieldOrTypeInstance(name) - if name == nil { - names = nil - } - } else { - // T P - typ = p.parseType() - } - } - } else { - // embedded, possibly generic type - // (using the enclosing parentheses to distinguish it from a named field declaration) - // TODO(rFindley) confirm that this doesn't allow parenthesized embedded type - typ = p.parseType() - } - - var tag *ast.BasicLit - if p.tok == token.STRING { - tag = &ast.BasicLit{ValuePos: p.pos, Kind: p.tok, Value: p.lit} - p.next() - } - - p.expectSemi() // call before accessing p.linecomment - - field := &ast.Field{Doc: doc, Names: names, Type: typ, Tag: tag, Comment: p.lineComment} - return field -} - -func (p *parser) parseStructType() *ast.StructType { - if p.trace { - defer un(trace(p, "StructType")) - } - - pos := p.expect(token.STRUCT) - lbrace := p.expect(token.LBRACE) - var list []*ast.Field - for p.tok == token.IDENT || p.tok == token.MUL || p.tok == token.LPAREN { - // a field declaration cannot start with a '(' but we accept - // it here for more robust parsing and better error messages - // (parseFieldDecl will check and complain if necessary) - list = append(list, p.parseFieldDecl()) - } - rbrace := p.expect(token.RBRACE) - - return &ast.StructType{ - Struct: pos, - Fields: &ast.FieldList{ - Opening: lbrace, - List: list, - Closing: rbrace, - }, - } -} - -func (p *parser) parsePointerType() *ast.StarExpr { - if p.trace { - defer un(trace(p, "PointerType")) - } - - star := p.expect(token.MUL) - base := p.parseType() - - return &ast.StarExpr{Star: star, X: base} -} - -func (p *parser) parseDotsType() *ast.Ellipsis { - if p.trace { - defer un(trace(p, "DotsType")) - } - - pos := p.expect(token.ELLIPSIS) - elt := p.parseType() - - return &ast.Ellipsis{Ellipsis: pos, Elt: elt} -} - -type field struct { - name *ast.Ident - typ ast.Expr -} - -func (p *parser) parseParamDecl(name *ast.Ident, typeSetsOK bool) (f field) { - // TODO(rFindley) refactor to be more similar to paramDeclOrNil in the syntax - // package - if p.trace { - defer un(trace(p, "ParamDeclOrNil")) - } - - ptok := p.tok - if name != nil { - p.tok = token.IDENT // force token.IDENT case in switch below - } else if typeSetsOK && p.tok == token.TILDE { - // "~" ... - return field{nil, p.embeddedElem(nil)} - } - - switch p.tok { - case token.IDENT: - // name - if name != nil { - f.name = name - p.tok = ptok - } else { - f.name = p.parseIdent() - } - switch p.tok { - case token.IDENT, token.MUL, token.ARROW, token.FUNC, token.CHAN, token.MAP, token.STRUCT, token.INTERFACE, token.LPAREN: - // name type - f.typ = p.parseType() - - case token.LBRACK: - // name "[" type1, ..., typeN "]" or name "[" n "]" type - f.name, f.typ = p.parseArrayFieldOrTypeInstance(f.name) - - case token.ELLIPSIS: - // name "..." type - f.typ = p.parseDotsType() - return // don't allow ...type "|" ... - - case token.PERIOD: - // name "." ... - f.typ = p.parseQualifiedIdent(f.name) - f.name = nil - - case token.TILDE: - if typeSetsOK { - f.typ = p.embeddedElem(nil) - return - } - - case token.OR: - if typeSetsOK { - // name "|" typeset - f.typ = p.embeddedElem(f.name) - f.name = nil - return - } - } - - case token.MUL, token.ARROW, token.FUNC, token.LBRACK, token.CHAN, token.MAP, token.STRUCT, token.INTERFACE, token.LPAREN: - // type - f.typ = p.parseType() - - case token.ELLIPSIS: - // "..." type - // (always accepted) - f.typ = p.parseDotsType() - return // don't allow ...type "|" ... - - default: - // TODO(rfindley): this is incorrect in the case of type parameter lists - // (should be "']'" in that case) - p.errorExpected(p.pos, "')'") - p.advance(exprEnd) - } - - // [name] type "|" - if typeSetsOK && p.tok == token.OR && f.typ != nil { - f.typ = p.embeddedElem(f.typ) - } - - return -} - -func (p *parser) parseParameterList(name0 *ast.Ident, typ0 ast.Expr, closing token.Token) (params []*ast.Field) { - if p.trace { - defer un(trace(p, "ParameterList")) - } - - // Type parameters are the only parameter list closed by ']'. - tparams := closing == token.RBRACK - // Type set notation is ok in type parameter lists. - typeSetsOK := tparams && p.allowTypeSets() - - pos := p.pos - if name0 != nil { - pos = name0.Pos() - } - - var list []field - var named int // number of parameters that have an explicit name and type - - for name0 != nil || p.tok != closing && p.tok != token.EOF { - var par field - if typ0 != nil { - if typeSetsOK { - typ0 = p.embeddedElem(typ0) - } - par = field{name0, typ0} - } else { - par = p.parseParamDecl(name0, typeSetsOK) - } - name0 = nil // 1st name was consumed if present - typ0 = nil // 1st typ was consumed if present - if par.name != nil || par.typ != nil { - list = append(list, par) - if par.name != nil && par.typ != nil { - named++ - } - } - if !p.atComma("parameter list", closing) { - break - } - p.next() - } - - if len(list) == 0 { - return // not uncommon - } - - // TODO(gri) parameter distribution and conversion to []*ast.Field - // can be combined and made more efficient - - // distribute parameter types - if named == 0 { - // all unnamed => found names are type names - for i := 0; i < len(list); i++ { - par := &list[i] - if typ := par.name; typ != nil { - par.typ = typ - par.name = nil - } - } - if tparams { - p.error(pos, "all type parameters must be named") - } - } else if named != len(list) { - // some named => all must be named - ok := true - var typ ast.Expr - missingName := pos - for i := len(list) - 1; i >= 0; i-- { - if par := &list[i]; par.typ != nil { - typ = par.typ - if par.name == nil { - ok = false - missingName = par.typ.Pos() - n := ast.NewIdent("_") - n.NamePos = typ.Pos() // correct position - par.name = n - } - } else if typ != nil { - par.typ = typ - } else { - // par.typ == nil && typ == nil => we only have a par.name - ok = false - missingName = par.name.Pos() - par.typ = &ast.BadExpr{From: par.name.Pos(), To: p.pos} - } - } - if !ok { - if tparams { - p.error(missingName, "all type parameters must be named") - } else { - p.error(pos, "mixed named and unnamed parameters") - } - } - } - - // convert list []*ast.Field - if named == 0 { - // parameter list consists of types only - for _, par := range list { - assert(par.typ != nil, "nil type in unnamed parameter list") - params = append(params, &ast.Field{Type: par.typ}) - } - return - } - - // parameter list consists of named parameters with types - var names []*ast.Ident - var typ ast.Expr - addParams := func() { - assert(typ != nil, "nil type in named parameter list") - field := &ast.Field{Names: names, Type: typ} - params = append(params, field) - names = nil - } - for _, par := range list { - if par.typ != typ { - if len(names) > 0 { - addParams() - } - typ = par.typ - } - names = append(names, par.name) - } - if len(names) > 0 { - addParams() - } - return -} - -func (p *parser) parseParameters(acceptTParams bool) (tparams, params *ast.FieldList) { - if p.trace { - defer un(trace(p, "Parameters")) - } - - if p.allowGenerics() && acceptTParams && p.tok == token.LBRACK { - opening := p.pos - p.next() - // [T any](params) syntax - list := p.parseParameterList(nil, nil, token.RBRACK) - rbrack := p.expect(token.RBRACK) - tparams = &ast.FieldList{Opening: opening, List: list, Closing: rbrack} - // Type parameter lists must not be empty. - if tparams.NumFields() == 0 { - p.error(tparams.Closing, "empty type parameter list") - tparams = nil // avoid follow-on errors - } - } - - opening := p.expect(token.LPAREN) - - var fields []*ast.Field - if p.tok != token.RPAREN { - fields = p.parseParameterList(nil, nil, token.RPAREN) - } - - rparen := p.expect(token.RPAREN) - params = &ast.FieldList{Opening: opening, List: fields, Closing: rparen} - - return -} - -func (p *parser) parseResult() *ast.FieldList { - if p.trace { - defer un(trace(p, "Result")) - } - - if p.tok == token.LPAREN { - _, results := p.parseParameters(false) - return results - } - - typ := p.tryIdentOrType() - if typ != nil { - list := make([]*ast.Field, 1) - list[0] = &ast.Field{Type: typ} - return &ast.FieldList{List: list} - } - - return nil -} - -func (p *parser) parseFuncType() *ast.FuncType { - if p.trace { - defer un(trace(p, "FuncType")) - } - - pos := p.expect(token.FUNC) - tparams, params := p.parseParameters(true) - if tparams != nil { - p.error(tparams.Pos(), "function type must have no type parameters") - } - results := p.parseResult() - - return &ast.FuncType{Func: pos, Params: params, Results: results} -} - -func (p *parser) parseMethodSpec() *ast.Field { - if p.trace { - defer un(trace(p, "MethodSpec")) - } - - doc := p.leadComment - var idents []*ast.Ident - var typ ast.Expr - x := p.parseTypeName(nil) - if ident, _ := x.(*ast.Ident); ident != nil { - switch { - case p.tok == token.LBRACK && p.allowGenerics(): - // generic method or embedded instantiated type - lbrack := p.pos - p.next() - p.exprLev++ - x := p.parseExpr() - p.exprLev-- - if name0, _ := x.(*ast.Ident); name0 != nil && p.tok != token.COMMA && p.tok != token.RBRACK { - // generic method m[T any] - // - // Interface methods do not have type parameters. We parse them for a - // better error message and improved error recovery. - _ = p.parseParameterList(name0, nil, token.RBRACK) - _ = p.expect(token.RBRACK) - p.error(lbrack, "interface method must have no type parameters") - - // TODO(rfindley) refactor to share code with parseFuncType. - _, params := p.parseParameters(false) - results := p.parseResult() - idents = []*ast.Ident{ident} - typ = &ast.FuncType{ - Func: token.NoPos, - Params: params, - Results: results, - } - } else { - // embedded instantiated type - // TODO(rfindley) should resolve all identifiers in x. - list := []ast.Expr{x} - if p.atComma("type argument list", token.RBRACK) { - p.exprLev++ - p.next() - for p.tok != token.RBRACK && p.tok != token.EOF { - list = append(list, p.parseType()) - if !p.atComma("type argument list", token.RBRACK) { - break - } - p.next() - } - p.exprLev-- - } - rbrack := p.expectClosing(token.RBRACK, "type argument list") - typ = typeparams.PackIndexExpr(ident, lbrack, list, rbrack) - } - case p.tok == token.LPAREN: - // ordinary method - // TODO(rfindley) refactor to share code with parseFuncType. - _, params := p.parseParameters(false) - results := p.parseResult() - idents = []*ast.Ident{ident} - typ = &ast.FuncType{Func: token.NoPos, Params: params, Results: results} - default: - // embedded type - typ = x - } - } else { - // embedded, possibly instantiated type - typ = x - if p.tok == token.LBRACK && p.allowGenerics() { - // embedded instantiated interface - typ = p.parseTypeInstance(typ) - } - } - - // Comment is added at the callsite: the field below may joined with - // additional type specs using '|'. - // TODO(rfindley) this should be refactored. - // TODO(rfindley) add more tests for comment handling. - return &ast.Field{Doc: doc, Names: idents, Type: typ} -} - -func (p *parser) embeddedElem(x ast.Expr) ast.Expr { - if p.trace { - defer un(trace(p, "EmbeddedElem")) - } - if x == nil { - x = p.embeddedTerm() - } - for p.tok == token.OR { - t := new(ast.BinaryExpr) - t.OpPos = p.pos - t.Op = token.OR - p.next() - t.X = x - t.Y = p.embeddedTerm() - x = t - } - return x -} - -func (p *parser) embeddedTerm() ast.Expr { - if p.trace { - defer un(trace(p, "EmbeddedTerm")) - } - if p.tok == token.TILDE { - t := new(ast.UnaryExpr) - t.OpPos = p.pos - t.Op = token.TILDE - p.next() - t.X = p.parseType() - return t - } - - t := p.tryIdentOrType() - if t == nil { - pos := p.pos - p.errorExpected(pos, "~ term or type") - p.advance(exprEnd) - return &ast.BadExpr{From: pos, To: p.pos} - } - - return t -} - -func (p *parser) parseInterfaceType() *ast.InterfaceType { - if p.trace { - defer un(trace(p, "InterfaceType")) - } - - pos := p.expect(token.INTERFACE) - lbrace := p.expect(token.LBRACE) - - var list []*ast.Field - -parseElements: - for { - switch { - case p.tok == token.IDENT: - f := p.parseMethodSpec() - if f.Names == nil && p.allowGenerics() { - f.Type = p.embeddedElem(f.Type) - } - p.expectSemi() - f.Comment = p.lineComment - list = append(list, f) - case p.tok == token.TILDE && p.allowGenerics(): - typ := p.embeddedElem(nil) - p.expectSemi() - comment := p.lineComment - list = append(list, &ast.Field{Type: typ, Comment: comment}) - case p.allowGenerics(): - if t := p.tryIdentOrType(); t != nil { - typ := p.embeddedElem(t) - p.expectSemi() - comment := p.lineComment - list = append(list, &ast.Field{Type: typ, Comment: comment}) - } else { - break parseElements - } - default: - break parseElements - } - } - - // TODO(rfindley): the error produced here could be improved, since we could - // accept a identifier, 'type', or a '}' at this point. - rbrace := p.expect(token.RBRACE) - - return &ast.InterfaceType{ - Interface: pos, - Methods: &ast.FieldList{ - Opening: lbrace, - List: list, - Closing: rbrace, - }, - } -} - -func (p *parser) parseMapType() *ast.MapType { - if p.trace { - defer un(trace(p, "MapType")) - } - - pos := p.expect(token.MAP) - p.expect(token.LBRACK) - key := p.parseType() - p.expect(token.RBRACK) - value := p.parseType() - - return &ast.MapType{Map: pos, Key: key, Value: value} -} - -func (p *parser) parseChanType() *ast.ChanType { - if p.trace { - defer un(trace(p, "ChanType")) - } - - pos := p.pos - dir := ast.SEND | ast.RECV - var arrow token.Pos - if p.tok == token.CHAN { - p.next() - if p.tok == token.ARROW { - arrow = p.pos - p.next() - dir = ast.SEND - } - } else { - arrow = p.expect(token.ARROW) - p.expect(token.CHAN) - dir = ast.RECV - } - value := p.parseType() - - return &ast.ChanType{Begin: pos, Arrow: arrow, Dir: dir, Value: value} -} - -func (p *parser) parseTypeInstance(typ ast.Expr) ast.Expr { - assert(p.allowGenerics(), "parseTypeInstance while not parsing type params") - if p.trace { - defer un(trace(p, "TypeInstance")) - } - - opening := p.expect(token.LBRACK) - p.exprLev++ - var list []ast.Expr - for p.tok != token.RBRACK && p.tok != token.EOF { - list = append(list, p.parseType()) - if !p.atComma("type argument list", token.RBRACK) { - break - } - p.next() - } - p.exprLev-- - - closing := p.expectClosing(token.RBRACK, "type argument list") - - if len(list) == 0 { - p.errorExpected(closing, "type argument list") - return &ast.IndexExpr{ - X: typ, - Lbrack: opening, - Index: &ast.BadExpr{From: opening + 1, To: closing}, - Rbrack: closing, - } - } - - return typeparams.PackIndexExpr(typ, opening, list, closing) -} - -func (p *parser) tryIdentOrType() ast.Expr { - defer decNestLev(incNestLev(p)) - - switch p.tok { - case token.IDENT: - typ := p.parseTypeName(nil) - if p.tok == token.LBRACK && p.allowGenerics() { - typ = p.parseTypeInstance(typ) - } - return typ - case token.LBRACK: - lbrack := p.expect(token.LBRACK) - return p.parseArrayType(lbrack, nil) - case token.STRUCT: - return p.parseStructType() - case token.MUL: - return p.parsePointerType() - case token.FUNC: - typ := p.parseFuncType() - return typ - case token.INTERFACE: - return p.parseInterfaceType() - case token.MAP: - return p.parseMapType() - case token.CHAN, token.ARROW: - return p.parseChanType() - case token.LPAREN: - lparen := p.pos - p.next() - typ := p.parseType() - rparen := p.expect(token.RPAREN) - return &ast.ParenExpr{Lparen: lparen, X: typ, Rparen: rparen} - } - - // no type found - return nil -} - -// ---------------------------------------------------------------------------- -// Blocks - -func (p *parser) parseStmtList() (list []ast.Stmt) { - if p.trace { - defer un(trace(p, "StatementList")) - } - - for p.tok != token.CASE && p.tok != token.DEFAULT && p.tok != token.RBRACE && p.tok != token.EOF { - list = append(list, p.parseStmt()) - } - - return -} - -func (p *parser) parseBody() *ast.BlockStmt { - if p.trace { - defer un(trace(p, "Body")) - } - - lbrace := p.expect(token.LBRACE) - list := p.parseStmtList() - rbrace := p.expect2(token.RBRACE) - - return &ast.BlockStmt{Lbrace: lbrace, List: list, Rbrace: rbrace} -} - -func (p *parser) parseBlockStmt() *ast.BlockStmt { - if p.trace { - defer un(trace(p, "BlockStmt")) - } - - lbrace := p.expect(token.LBRACE) - list := p.parseStmtList() - rbrace := p.expect2(token.RBRACE) - - return &ast.BlockStmt{Lbrace: lbrace, List: list, Rbrace: rbrace} -} - -// ---------------------------------------------------------------------------- -// Expressions - -func (p *parser) parseFuncTypeOrLit() ast.Expr { - if p.trace { - defer un(trace(p, "FuncTypeOrLit")) - } - - typ := p.parseFuncType() - if p.tok != token.LBRACE { - // function type only - return typ - } - - p.exprLev++ - body := p.parseBody() - p.exprLev-- - - return &ast.FuncLit{Type: typ, Body: body} -} - -// parseOperand may return an expression or a raw type (incl. array -// types of the form [...]T. Callers must verify the result. -func (p *parser) parseOperand() ast.Expr { - if p.trace { - defer un(trace(p, "Operand")) - } - - switch p.tok { - case token.IDENT: - x := p.parseIdent() - return x - - case token.INT, token.FLOAT, token.IMAG, token.CHAR, token.STRING: - x := &ast.BasicLit{ValuePos: p.pos, Kind: p.tok, Value: p.lit} - p.next() - return x - - case token.LPAREN: - lparen := p.pos - p.next() - p.exprLev++ - x := p.parseRhsOrType() // types may be parenthesized: (some type) - p.exprLev-- - rparen := p.expect(token.RPAREN) - return &ast.ParenExpr{Lparen: lparen, X: x, Rparen: rparen} - - case token.FUNC: - return p.parseFuncTypeOrLit() - } - - if typ := p.tryIdentOrType(); typ != nil { // do not consume trailing type parameters - // could be type for composite literal or conversion - _, isIdent := typ.(*ast.Ident) - assert(!isIdent, "type cannot be identifier") - return typ - } - - // we have an error - pos := p.pos - p.errorExpected(pos, "operand") - p.advance(stmtStart) - return &ast.BadExpr{From: pos, To: p.pos} -} - -func (p *parser) parseSelector(x ast.Expr) ast.Expr { - if p.trace { - defer un(trace(p, "Selector")) - } - - sel := p.parseIdent() - - return &ast.SelectorExpr{X: x, Sel: sel} -} - -func (p *parser) parseTypeAssertion(x ast.Expr) ast.Expr { - if p.trace { - defer un(trace(p, "TypeAssertion")) - } - - lparen := p.expect(token.LPAREN) - var typ ast.Expr - if p.tok == token.TYPE { - // type switch: typ == nil - p.next() - } else { - typ = p.parseType() - } - rparen := p.expect(token.RPAREN) - - return &ast.TypeAssertExpr{X: x, Type: typ, Lparen: lparen, Rparen: rparen} -} - -func (p *parser) parseIndexOrSliceOrInstance(x ast.Expr) ast.Expr { - if p.trace { - defer un(trace(p, "parseIndexOrSliceOrInstance")) - } - - lbrack := p.expect(token.LBRACK) - if p.tok == token.RBRACK { - // empty index, slice or index expressions are not permitted; - // accept them for parsing tolerance, but complain - p.errorExpected(p.pos, "operand") - rbrack := p.pos - p.next() - return &ast.IndexExpr{ - X: x, - Lbrack: lbrack, - Index: &ast.BadExpr{From: rbrack, To: rbrack}, - Rbrack: rbrack, - } - } - p.exprLev++ - - const N = 3 // change the 3 to 2 to disable 3-index slices - var args []ast.Expr - var index [N]ast.Expr - var colons [N - 1]token.Pos - var firstComma token.Pos - if p.tok != token.COLON { - // We can't know if we have an index expression or a type instantiation; - // so even if we see a (named) type we are not going to be in type context. - index[0] = p.parseRhsOrType() - } - ncolons := 0 - switch p.tok { - case token.COLON: - // slice expression - for p.tok == token.COLON && ncolons < len(colons) { - colons[ncolons] = p.pos - ncolons++ - p.next() - if p.tok != token.COLON && p.tok != token.RBRACK && p.tok != token.EOF { - index[ncolons] = p.parseRhs() - } - } - case token.COMMA: - firstComma = p.pos - // instance expression - args = append(args, index[0]) - for p.tok == token.COMMA { - p.next() - if p.tok != token.RBRACK && p.tok != token.EOF { - args = append(args, p.parseType()) - } - } - } - - p.exprLev-- - rbrack := p.expect(token.RBRACK) - - if ncolons > 0 { - // slice expression - slice3 := false - if ncolons == 2 { - slice3 = true - // Check presence of 2nd and 3rd index here rather than during type-checking - // to prevent erroneous programs from passing through gofmt (was issue 7305). - if index[1] == nil { - p.error(colons[0], "2nd index required in 3-index slice") - index[1] = &ast.BadExpr{From: colons[0] + 1, To: colons[1]} - } - if index[2] == nil { - p.error(colons[1], "3rd index required in 3-index slice") - index[2] = &ast.BadExpr{From: colons[1] + 1, To: rbrack} - } - } - return &ast.SliceExpr{X: x, Lbrack: lbrack, Low: index[0], High: index[1], Max: index[2], Slice3: slice3, Rbrack: rbrack} - } - - if len(args) == 0 { - // index expression - return &ast.IndexExpr{X: x, Lbrack: lbrack, Index: index[0], Rbrack: rbrack} - } - - if !p.allowGenerics() { - p.error(firstComma, "expected ']' or ':', found ','") - return &ast.BadExpr{From: args[0].Pos(), To: args[len(args)-1].End()} - } - - // instance expression - return typeparams.PackIndexExpr(x, lbrack, args, rbrack) -} - -func (p *parser) parseCallOrConversion(fun ast.Expr) *ast.CallExpr { - if p.trace { - defer un(trace(p, "CallOrConversion")) - } - - lparen := p.expect(token.LPAREN) - p.exprLev++ - var list []ast.Expr - var ellipsis token.Pos - for p.tok != token.RPAREN && p.tok != token.EOF && !ellipsis.IsValid() { - list = append(list, p.parseRhsOrType()) // builtins may expect a type: make(some type, ...) - if p.tok == token.ELLIPSIS { - ellipsis = p.pos - p.next() - } - if !p.atComma("argument list", token.RPAREN) { - break - } - p.next() - } - p.exprLev-- - rparen := p.expectClosing(token.RPAREN, "argument list") - - return &ast.CallExpr{Fun: fun, Lparen: lparen, Args: list, Ellipsis: ellipsis, Rparen: rparen} -} - -func (p *parser) parseValue() ast.Expr { - if p.trace { - defer un(trace(p, "Element")) - } - - if p.tok == token.LBRACE { - return p.parseLiteralValue(nil) - } - - x := p.checkExpr(p.parseExpr()) - - return x -} - -func (p *parser) parseElement() ast.Expr { - if p.trace { - defer un(trace(p, "Element")) - } - - x := p.parseValue() - if p.tok == token.COLON { - colon := p.pos - p.next() - x = &ast.KeyValueExpr{Key: x, Colon: colon, Value: p.parseValue()} - } - - return x -} - -func (p *parser) parseElementList() (list []ast.Expr) { - if p.trace { - defer un(trace(p, "ElementList")) - } - - for p.tok != token.RBRACE && p.tok != token.EOF { - list = append(list, p.parseElement()) - if !p.atComma("composite literal", token.RBRACE) { - break - } - p.next() - } - - return -} - -func (p *parser) parseLiteralValue(typ ast.Expr) ast.Expr { - if p.trace { - defer un(trace(p, "LiteralValue")) - } - - lbrace := p.expect(token.LBRACE) - var elts []ast.Expr - p.exprLev++ - if p.tok != token.RBRACE { - elts = p.parseElementList() - } - p.exprLev-- - rbrace := p.expectClosing(token.RBRACE, "composite literal") - return &ast.CompositeLit{Type: typ, Lbrace: lbrace, Elts: elts, Rbrace: rbrace} -} - -// checkExpr checks that x is an expression (and not a type). -func (p *parser) checkExpr(x ast.Expr) ast.Expr { - switch unparen(x).(type) { - case *ast.BadExpr: - case *ast.Ident: - case *ast.BasicLit: - case *ast.FuncLit: - case *ast.CompositeLit: - case *ast.ParenExpr: - panic("unreachable") - case *ast.SelectorExpr: - case *ast.IndexExpr: - case *ast.IndexListExpr: - case *ast.SliceExpr: - case *ast.TypeAssertExpr: - // If t.Type == nil we have a type assertion of the form - // y.(type), which is only allowed in type switch expressions. - // It's hard to exclude those but for the case where we are in - // a type switch. Instead be lenient and test this in the type - // checker. - case *ast.CallExpr: - case *ast.StarExpr: - case *ast.UnaryExpr: - case *ast.BinaryExpr: - default: - // all other nodes are not proper expressions - p.errorExpected(x.Pos(), "expression") - x = &ast.BadExpr{From: x.Pos(), To: p.safePos(x.End())} - } - return x -} - -// If x is of the form (T), unparen returns unparen(T), otherwise it returns x. -func unparen(x ast.Expr) ast.Expr { - if p, isParen := x.(*ast.ParenExpr); isParen { - x = unparen(p.X) - } - return x -} - -// checkExprOrType checks that x is an expression or a type -// (and not a raw type such as [...]T). -func (p *parser) checkExprOrType(x ast.Expr) ast.Expr { - switch t := unparen(x).(type) { - case *ast.ParenExpr: - panic("unreachable") - case *ast.ArrayType: - if len, isEllipsis := t.Len.(*ast.Ellipsis); isEllipsis { - p.error(len.Pos(), "expected array length, found '...'") - x = &ast.BadExpr{From: x.Pos(), To: p.safePos(x.End())} - } - } - - // all other nodes are expressions or types - return x -} - -func (p *parser) parsePrimaryExpr(x ast.Expr) ast.Expr { - if p.trace { - defer un(trace(p, "PrimaryExpr")) - } - - if x == nil { - x = p.parseOperand() - } - // We track the nesting here rather than at the entry for the function, - // since it can iteratively produce a nested output, and we want to - // limit how deep a structure we generate. - var n int - defer func() { p.nestLev -= n }() - for n = 1; ; n++ { - incNestLev(p) - switch p.tok { - case token.PERIOD: - p.next() - switch p.tok { - case token.IDENT: - x = p.parseSelector(p.checkExprOrType(x)) - case token.LPAREN: - x = p.parseTypeAssertion(p.checkExpr(x)) - default: - pos := p.pos - p.errorExpected(pos, "selector or type assertion") - // TODO(rFindley) The check for token.RBRACE below is a targeted fix - // to error recovery sufficient to make the x/tools tests to - // pass with the new parsing logic introduced for type - // parameters. Remove this once error recovery has been - // more generally reconsidered. - if p.tok != token.RBRACE { - p.next() // make progress - } - sel := &ast.Ident{NamePos: pos, Name: "_"} - x = &ast.SelectorExpr{X: x, Sel: sel} - } - case token.LBRACK: - x = p.parseIndexOrSliceOrInstance(p.checkExpr(x)) - case token.LPAREN: - x = p.parseCallOrConversion(p.checkExprOrType(x)) - case token.LBRACE: - // operand may have returned a parenthesized complit - // type; accept it but complain if we have a complit - t := unparen(x) - // determine if '{' belongs to a composite literal or a block statement - switch t.(type) { - case *ast.BadExpr, *ast.Ident, *ast.SelectorExpr: - if p.exprLev < 0 { - return x - } - // x is possibly a composite literal type - case *ast.IndexExpr, *ast.IndexListExpr: - if p.exprLev < 0 { - return x - } - // x is possibly a composite literal type - case *ast.ArrayType, *ast.StructType, *ast.MapType: - // x is a composite literal type - default: - return x - } - if t != x { - p.error(t.Pos(), "cannot parenthesize type in composite literal") - // already progressed, no need to advance - } - x = p.parseLiteralValue(x) - default: - return x - } - } -} - -func (p *parser) parseUnaryExpr() ast.Expr { - defer decNestLev(incNestLev(p)) - - if p.trace { - defer un(trace(p, "UnaryExpr")) - } - - switch p.tok { - case token.ADD, token.SUB, token.NOT, token.XOR, token.AND, token.TILDE: - pos, op := p.pos, p.tok - p.next() - x := p.parseUnaryExpr() - return &ast.UnaryExpr{OpPos: pos, Op: op, X: p.checkExpr(x)} - - case token.ARROW: - // channel type or receive expression - arrow := p.pos - p.next() - - // If the next token is token.CHAN we still don't know if it - // is a channel type or a receive operation - we only know - // once we have found the end of the unary expression. There - // are two cases: - // - // <- type => (<-type) must be channel type - // <- expr => <-(expr) is a receive from an expression - // - // In the first case, the arrow must be re-associated with - // the channel type parsed already: - // - // <- (chan type) => (<-chan type) - // <- (chan<- type) => (<-chan (<-type)) - - x := p.parseUnaryExpr() - - // determine which case we have - if typ, ok := x.(*ast.ChanType); ok { - // (<-type) - - // re-associate position info and <- - dir := ast.SEND - for ok && dir == ast.SEND { - if typ.Dir == ast.RECV { - // error: (<-type) is (<-(<-chan T)) - p.errorExpected(typ.Arrow, "'chan'") - } - arrow, typ.Begin, typ.Arrow = typ.Arrow, arrow, arrow - dir, typ.Dir = typ.Dir, ast.RECV - typ, ok = typ.Value.(*ast.ChanType) - } - if dir == ast.SEND { - p.errorExpected(arrow, "channel type") - } - - return x - } - - // <-(expr) - return &ast.UnaryExpr{OpPos: arrow, Op: token.ARROW, X: p.checkExpr(x)} - - case token.MUL: - // pointer type or unary "*" expression - pos := p.pos - p.next() - x := p.parseUnaryExpr() - return &ast.StarExpr{Star: pos, X: p.checkExprOrType(x)} - } - - return p.parsePrimaryExpr(nil) -} - -func (p *parser) tokPrec() (token.Token, int) { - tok := p.tok - if p.inRhs && tok == token.ASSIGN { - tok = token.EQL - } - return tok, tok.Precedence() -} - -// parseBinaryExpr parses a (possibly) binary expression. -// If x is non-nil, it is used as the left operand. -// If check is true, operands are checked to be valid expressions. -// -// TODO(rfindley): parseBinaryExpr has become overloaded. Consider refactoring. -func (p *parser) parseBinaryExpr(x ast.Expr, prec1 int, check bool) ast.Expr { - if p.trace { - defer un(trace(p, "BinaryExpr")) - } - - if x == nil { - x = p.parseUnaryExpr() - } - // We track the nesting here rather than at the entry for the function, - // since it can iteratively produce a nested output, and we want to - // limit how deep a structure we generate. - var n int - defer func() { p.nestLev -= n }() - for n = 1; ; n++ { - incNestLev(p) - op, oprec := p.tokPrec() - if oprec < prec1 { - return x - } - pos := p.expect(op) - y := p.parseBinaryExpr(nil, oprec+1, check) - if check { - x = p.checkExpr(x) - y = p.checkExpr(y) - } - x = &ast.BinaryExpr{X: x, OpPos: pos, Op: op, Y: y} - } -} - -// The result may be a type or even a raw type ([...]int). Callers must -// check the result (using checkExpr or checkExprOrType), depending on -// context. -func (p *parser) parseExpr() ast.Expr { - if p.trace { - defer un(trace(p, "Expression")) - } - - return p.parseBinaryExpr(nil, token.LowestPrec+1, true) -} - -func (p *parser) parseRhs() ast.Expr { - old := p.inRhs - p.inRhs = true - x := p.checkExpr(p.parseExpr()) - p.inRhs = old - return x -} - -func (p *parser) parseRhsOrType() ast.Expr { - old := p.inRhs - p.inRhs = true - x := p.checkExprOrType(p.parseExpr()) - p.inRhs = old - return x -} - -// ---------------------------------------------------------------------------- -// Statements - -// Parsing modes for parseSimpleStmt. -const ( - basic = iota - labelOk - rangeOk -) - -// parseSimpleStmt returns true as 2nd result if it parsed the assignment -// of a range clause (with mode == rangeOk). The returned statement is an -// assignment with a right-hand side that is a single unary expression of -// the form "range x". No guarantees are given for the left-hand side. -func (p *parser) parseSimpleStmt(mode int) (ast.Stmt, bool) { - if p.trace { - defer un(trace(p, "SimpleStmt")) - } - - x := p.parseList(false) - - switch p.tok { - case - token.DEFINE, token.ASSIGN, token.ADD_ASSIGN, - token.SUB_ASSIGN, token.MUL_ASSIGN, token.QUO_ASSIGN, - token.REM_ASSIGN, token.AND_ASSIGN, token.OR_ASSIGN, - token.XOR_ASSIGN, token.SHL_ASSIGN, token.SHR_ASSIGN, token.AND_NOT_ASSIGN: - // assignment statement, possibly part of a range clause - pos, tok := p.pos, p.tok - p.next() - var y []ast.Expr - isRange := false - if mode == rangeOk && p.tok == token.RANGE && (tok == token.DEFINE || tok == token.ASSIGN) { - pos := p.pos - p.next() - y = []ast.Expr{&ast.UnaryExpr{OpPos: pos, Op: token.RANGE, X: p.parseRhs()}} - isRange = true - } else { - y = p.parseList(true) - } - as := &ast.AssignStmt{Lhs: x, TokPos: pos, Tok: tok, Rhs: y} - if tok == token.DEFINE { - p.checkAssignStmt(as) - } - return as, isRange - } - - if len(x) > 1 { - p.errorExpected(x[0].Pos(), "1 expression") - // continue with first expression - } - - switch p.tok { - case token.COLON: - // labeled statement - colon := p.pos - p.next() - if label, isIdent := x[0].(*ast.Ident); mode == labelOk && isIdent { - // Go spec: The scope of a label is the body of the function - // in which it is declared and excludes the body of any nested - // function. - stmt := &ast.LabeledStmt{Label: label, Colon: colon, Stmt: p.parseStmt()} - return stmt, false - } - // The label declaration typically starts at x[0].Pos(), but the label - // declaration may be erroneous due to a token after that position (and - // before the ':'). If SpuriousErrors is not set, the (only) error - // reported for the line is the illegal label error instead of the token - // before the ':' that caused the problem. Thus, use the (latest) colon - // position for error reporting. - p.error(colon, "illegal label declaration") - return &ast.BadStmt{From: x[0].Pos(), To: colon + 1}, false - - case token.ARROW: - // send statement - arrow := p.pos - p.next() - y := p.parseRhs() - return &ast.SendStmt{Chan: x[0], Arrow: arrow, Value: y}, false - - case token.INC, token.DEC: - // increment or decrement - s := &ast.IncDecStmt{X: x[0], TokPos: p.pos, Tok: p.tok} - p.next() - return s, false - } - - // expression - return &ast.ExprStmt{X: x[0]}, false -} - -func (p *parser) checkAssignStmt(as *ast.AssignStmt) { - for _, x := range as.Lhs { - if _, isIdent := x.(*ast.Ident); !isIdent { - p.errorExpected(x.Pos(), "identifier on left side of :=") - } - } -} - -func (p *parser) parseCallExpr(callType string) *ast.CallExpr { - x := p.parseRhsOrType() // could be a conversion: (some type)(x) - if call, isCall := x.(*ast.CallExpr); isCall { - return call - } - if _, isBad := x.(*ast.BadExpr); !isBad { - // only report error if it's a new one - p.error(p.safePos(x.End()), fmt.Sprintf("function must be invoked in %s statement", callType)) - } - return nil -} - -func (p *parser) parseGoStmt() ast.Stmt { - if p.trace { - defer un(trace(p, "GoStmt")) - } - - pos := p.expect(token.GO) - call := p.parseCallExpr("go") - p.expectSemi() - if call == nil { - return &ast.BadStmt{From: pos, To: pos + 2} // len("go") - } - - return &ast.GoStmt{Go: pos, Call: call} -} - -func (p *parser) parseDeferStmt() ast.Stmt { - if p.trace { - defer un(trace(p, "DeferStmt")) - } - - pos := p.expect(token.DEFER) - call := p.parseCallExpr("defer") - p.expectSemi() - if call == nil { - return &ast.BadStmt{From: pos, To: pos + 5} // len("defer") - } - - return &ast.DeferStmt{Defer: pos, Call: call} -} - -func (p *parser) parseReturnStmt() *ast.ReturnStmt { - if p.trace { - defer un(trace(p, "ReturnStmt")) - } - - pos := p.pos - p.expect(token.RETURN) - var x []ast.Expr - if p.tok != token.SEMICOLON && p.tok != token.RBRACE { - x = p.parseList(true) - } - p.expectSemi() - - return &ast.ReturnStmt{Return: pos, Results: x} -} - -func (p *parser) parseBranchStmt(tok token.Token) *ast.BranchStmt { - if p.trace { - defer un(trace(p, "BranchStmt")) - } - - pos := p.expect(tok) - var label *ast.Ident - if tok != token.FALLTHROUGH && p.tok == token.IDENT { - label = p.parseIdent() - } - p.expectSemi() - - return &ast.BranchStmt{TokPos: pos, Tok: tok, Label: label} -} - -func (p *parser) makeExpr(s ast.Stmt, want string) ast.Expr { - if s == nil { - return nil - } - if es, isExpr := s.(*ast.ExprStmt); isExpr { - return p.checkExpr(es.X) - } - found := "simple statement" - if _, isAss := s.(*ast.AssignStmt); isAss { - found = "assignment" - } - p.error(s.Pos(), fmt.Sprintf("expected %s, found %s (missing parentheses around composite literal?)", want, found)) - return &ast.BadExpr{From: s.Pos(), To: p.safePos(s.End())} -} - -// parseIfHeader is an adjusted version of parser.header -// in cmd/compile/internal/syntax/parser.go, which has -// been tuned for better error handling. -func (p *parser) parseIfHeader() (init ast.Stmt, cond ast.Expr) { - if p.tok == token.LBRACE { - p.error(p.pos, "missing condition in if statement") - cond = &ast.BadExpr{From: p.pos, To: p.pos} - return - } - // p.tok != token.LBRACE - - prevLev := p.exprLev - p.exprLev = -1 - - if p.tok != token.SEMICOLON { - // accept potential variable declaration but complain - if p.tok == token.VAR { - p.next() - p.error(p.pos, "var declaration not allowed in 'IF' initializer") - } - init, _ = p.parseSimpleStmt(basic) - } - - var condStmt ast.Stmt - var semi struct { - pos token.Pos - lit string // ";" or "\n"; valid if pos.IsValid() - } - if p.tok != token.LBRACE { - if p.tok == token.SEMICOLON { - semi.pos = p.pos - semi.lit = p.lit - p.next() - } else { - p.expect(token.SEMICOLON) - } - if p.tok != token.LBRACE { - condStmt, _ = p.parseSimpleStmt(basic) - } - } else { - condStmt = init - init = nil - } - - if condStmt != nil { - cond = p.makeExpr(condStmt, "boolean expression") - } else if semi.pos.IsValid() { - if semi.lit == "\n" { - p.error(semi.pos, "unexpected newline, expecting { after if clause") - } else { - p.error(semi.pos, "missing condition in if statement") - } - } - - // make sure we have a valid AST - if cond == nil { - cond = &ast.BadExpr{From: p.pos, To: p.pos} - } - - p.exprLev = prevLev - return -} - -func (p *parser) parseIfStmt() *ast.IfStmt { - defer decNestLev(incNestLev(p)) - - if p.trace { - defer un(trace(p, "IfStmt")) - } - - pos := p.expect(token.IF) - - init, cond := p.parseIfHeader() - body := p.parseBlockStmt() - - var else_ ast.Stmt - if p.tok == token.ELSE { - p.next() - switch p.tok { - case token.IF: - else_ = p.parseIfStmt() - case token.LBRACE: - else_ = p.parseBlockStmt() - p.expectSemi() - default: - p.errorExpected(p.pos, "if statement or block") - else_ = &ast.BadStmt{From: p.pos, To: p.pos} - } - } else { - p.expectSemi() - } - - return &ast.IfStmt{If: pos, Init: init, Cond: cond, Body: body, Else: else_} -} - -func (p *parser) parseTypeList() (list []ast.Expr) { - if p.trace { - defer un(trace(p, "TypeList")) - } - - list = append(list, p.parseType()) - for p.tok == token.COMMA { - p.next() - list = append(list, p.parseType()) - } - - return -} - -func (p *parser) parseCaseClause(typeSwitch bool) *ast.CaseClause { - if p.trace { - defer un(trace(p, "CaseClause")) - } - - pos := p.pos - var list []ast.Expr - if p.tok == token.CASE { - p.next() - if typeSwitch { - list = p.parseTypeList() - } else { - list = p.parseList(true) - } - } else { - p.expect(token.DEFAULT) - } - - colon := p.expect(token.COLON) - body := p.parseStmtList() - - return &ast.CaseClause{Case: pos, List: list, Colon: colon, Body: body} -} - -func isTypeSwitchAssert(x ast.Expr) bool { - a, ok := x.(*ast.TypeAssertExpr) - return ok && a.Type == nil -} - -func (p *parser) isTypeSwitchGuard(s ast.Stmt) bool { - switch t := s.(type) { - case *ast.ExprStmt: - // x.(type) - return isTypeSwitchAssert(t.X) - case *ast.AssignStmt: - // v := x.(type) - if len(t.Lhs) == 1 && len(t.Rhs) == 1 && isTypeSwitchAssert(t.Rhs[0]) { - switch t.Tok { - case token.ASSIGN: - // permit v = x.(type) but complain - p.error(t.TokPos, "expected ':=', found '='") - fallthrough - case token.DEFINE: - return true - } - } - } - return false -} - -func (p *parser) parseSwitchStmt() ast.Stmt { - if p.trace { - defer un(trace(p, "SwitchStmt")) - } - - pos := p.expect(token.SWITCH) - - var s1, s2 ast.Stmt - if p.tok != token.LBRACE { - prevLev := p.exprLev - p.exprLev = -1 - if p.tok != token.SEMICOLON { - s2, _ = p.parseSimpleStmt(basic) - } - if p.tok == token.SEMICOLON { - p.next() - s1 = s2 - s2 = nil - if p.tok != token.LBRACE { - // A TypeSwitchGuard may declare a variable in addition - // to the variable declared in the initial SimpleStmt. - // Introduce extra scope to avoid redeclaration errors: - // - // switch t := 0; t := x.(T) { ... } - // - // (this code is not valid Go because the first t - // cannot be accessed and thus is never used, the extra - // scope is needed for the correct error message). - // - // If we don't have a type switch, s2 must be an expression. - // Having the extra nested but empty scope won't affect it. - s2, _ = p.parseSimpleStmt(basic) - } - } - p.exprLev = prevLev - } - - typeSwitch := p.isTypeSwitchGuard(s2) - lbrace := p.expect(token.LBRACE) - var list []ast.Stmt - for p.tok == token.CASE || p.tok == token.DEFAULT { - list = append(list, p.parseCaseClause(typeSwitch)) - } - rbrace := p.expect(token.RBRACE) - p.expectSemi() - body := &ast.BlockStmt{Lbrace: lbrace, List: list, Rbrace: rbrace} - - if typeSwitch { - return &ast.TypeSwitchStmt{Switch: pos, Init: s1, Assign: s2, Body: body} - } - - return &ast.SwitchStmt{Switch: pos, Init: s1, Tag: p.makeExpr(s2, "switch expression"), Body: body} -} - -func (p *parser) parseCommClause() *ast.CommClause { - if p.trace { - defer un(trace(p, "CommClause")) - } - - pos := p.pos - var comm ast.Stmt - if p.tok == token.CASE { - p.next() - lhs := p.parseList(false) - if p.tok == token.ARROW { - // SendStmt - if len(lhs) > 1 { - p.errorExpected(lhs[0].Pos(), "1 expression") - // continue with first expression - } - arrow := p.pos - p.next() - rhs := p.parseRhs() - comm = &ast.SendStmt{Chan: lhs[0], Arrow: arrow, Value: rhs} - } else { - // RecvStmt - if tok := p.tok; tok == token.ASSIGN || tok == token.DEFINE { - // RecvStmt with assignment - if len(lhs) > 2 { - p.errorExpected(lhs[0].Pos(), "1 or 2 expressions") - // continue with first two expressions - lhs = lhs[0:2] - } - pos := p.pos - p.next() - rhs := p.parseRhs() - as := &ast.AssignStmt{Lhs: lhs, TokPos: pos, Tok: tok, Rhs: []ast.Expr{rhs}} - if tok == token.DEFINE { - p.checkAssignStmt(as) - } - comm = as - } else { - // lhs must be single receive operation - if len(lhs) > 1 { - p.errorExpected(lhs[0].Pos(), "1 expression") - // continue with first expression - } - comm = &ast.ExprStmt{X: lhs[0]} - } - } - } else { - p.expect(token.DEFAULT) - } - - colon := p.expect(token.COLON) - body := p.parseStmtList() - - return &ast.CommClause{Case: pos, Comm: comm, Colon: colon, Body: body} -} - -func (p *parser) parseSelectStmt() *ast.SelectStmt { - if p.trace { - defer un(trace(p, "SelectStmt")) - } - - pos := p.expect(token.SELECT) - lbrace := p.expect(token.LBRACE) - var list []ast.Stmt - for p.tok == token.CASE || p.tok == token.DEFAULT { - list = append(list, p.parseCommClause()) - } - rbrace := p.expect(token.RBRACE) - p.expectSemi() - body := &ast.BlockStmt{Lbrace: lbrace, List: list, Rbrace: rbrace} - - return &ast.SelectStmt{Select: pos, Body: body} -} - -func (p *parser) parseForStmt() ast.Stmt { - if p.trace { - defer un(trace(p, "ForStmt")) - } - - pos := p.expect(token.FOR) - - var s1, s2, s3 ast.Stmt - var isRange bool - if p.tok != token.LBRACE { - prevLev := p.exprLev - p.exprLev = -1 - if p.tok != token.SEMICOLON { - if p.tok == token.RANGE { - // "for range x" (nil lhs in assignment) - pos := p.pos - p.next() - y := []ast.Expr{&ast.UnaryExpr{OpPos: pos, Op: token.RANGE, X: p.parseRhs()}} - s2 = &ast.AssignStmt{Rhs: y} - isRange = true - } else { - s2, isRange = p.parseSimpleStmt(rangeOk) - } - } - if !isRange && p.tok == token.SEMICOLON { - p.next() - s1 = s2 - s2 = nil - if p.tok != token.SEMICOLON { - s2, _ = p.parseSimpleStmt(basic) - } - p.expectSemi() - if p.tok != token.LBRACE { - s3, _ = p.parseSimpleStmt(basic) - } - } - p.exprLev = prevLev - } - - body := p.parseBlockStmt() - p.expectSemi() - - if isRange { - as := s2.(*ast.AssignStmt) - // check lhs - var key, value ast.Expr - switch len(as.Lhs) { - case 0: - // nothing to do - case 1: - key = as.Lhs[0] - case 2: - key, value = as.Lhs[0], as.Lhs[1] - default: - p.errorExpected(as.Lhs[len(as.Lhs)-1].Pos(), "at most 2 expressions") - return &ast.BadStmt{From: pos, To: p.safePos(body.End())} - } - // parseSimpleStmt returned a right-hand side that - // is a single unary expression of the form "range x" - x := as.Rhs[0].(*ast.UnaryExpr).X - return &ast.RangeStmt{ - For: pos, - Key: key, - Value: value, - TokPos: as.TokPos, - Tok: as.Tok, - X: x, - Body: body, - } - } - - // regular for statement - return &ast.ForStmt{ - For: pos, - Init: s1, - Cond: p.makeExpr(s2, "boolean or range expression"), - Post: s3, - Body: body, - } -} - -func (p *parser) parseStmt() (s ast.Stmt) { - defer decNestLev(incNestLev(p)) - - if p.trace { - defer un(trace(p, "Statement")) - } - - switch p.tok { - case token.CONST, token.TYPE, token.VAR: - s = &ast.DeclStmt{Decl: p.parseDecl(stmtStart)} - case - // tokens that may start an expression - token.IDENT, token.INT, token.FLOAT, token.IMAG, token.CHAR, token.STRING, token.FUNC, token.LPAREN, // operands - token.LBRACK, token.STRUCT, token.MAP, token.CHAN, token.INTERFACE, // composite types - token.ADD, token.SUB, token.MUL, token.AND, token.XOR, token.ARROW, token.NOT: // unary operators - s, _ = p.parseSimpleStmt(labelOk) - // because of the required look-ahead, labeled statements are - // parsed by parseSimpleStmt - don't expect a semicolon after - // them - if _, isLabeledStmt := s.(*ast.LabeledStmt); !isLabeledStmt { - p.expectSemi() - } - case token.GO: - s = p.parseGoStmt() - case token.DEFER: - s = p.parseDeferStmt() - case token.RETURN: - s = p.parseReturnStmt() - case token.BREAK, token.CONTINUE, token.GOTO, token.FALLTHROUGH: - s = p.parseBranchStmt(p.tok) - case token.LBRACE: - s = p.parseBlockStmt() - p.expectSemi() - case token.IF: - s = p.parseIfStmt() - case token.SWITCH: - s = p.parseSwitchStmt() - case token.SELECT: - s = p.parseSelectStmt() - case token.FOR: - s = p.parseForStmt() - case token.SEMICOLON: - // Is it ever possible to have an implicit semicolon - // producing an empty statement in a valid program? - // (handle correctly anyway) - s = &ast.EmptyStmt{Semicolon: p.pos, Implicit: p.lit == "\n"} - p.next() - case token.RBRACE: - // a semicolon may be omitted before a closing "}" - s = &ast.EmptyStmt{Semicolon: p.pos, Implicit: true} - default: - // no statement found - pos := p.pos - p.errorExpected(pos, "statement") - p.advance(stmtStart) - s = &ast.BadStmt{From: pos, To: p.pos} - } - - return -} - -// ---------------------------------------------------------------------------- -// Declarations - -type parseSpecFunction func(doc *ast.CommentGroup, pos token.Pos, keyword token.Token, iota int) ast.Spec - -func isValidImport(lit string) bool { - const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD" - s, _ := strconv.Unquote(lit) // go/scanner returns a legal string literal - for _, r := range s { - if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) { - return false - } - } - return s != "" -} - -func (p *parser) parseImportSpec(doc *ast.CommentGroup, _ token.Pos, _ token.Token, _ int) ast.Spec { - if p.trace { - defer un(trace(p, "ImportSpec")) - } - - var ident *ast.Ident - switch p.tok { - case token.PERIOD: - ident = &ast.Ident{NamePos: p.pos, Name: "."} - p.next() - case token.IDENT: - ident = p.parseIdent() - } - - pos := p.pos - var path string - if p.tok == token.STRING { - path = p.lit - if !isValidImport(path) { - p.error(pos, "invalid import path: "+path) - } - p.next() - } else { - p.expect(token.STRING) // use expect() error handling - } - p.expectSemi() // call before accessing p.linecomment - - // collect imports - spec := &ast.ImportSpec{ - Doc: doc, - Name: ident, - Path: &ast.BasicLit{ValuePos: pos, Kind: token.STRING, Value: path}, - Comment: p.lineComment, - } - p.imports = append(p.imports, spec) - - return spec -} - -func (p *parser) parseValueSpec(doc *ast.CommentGroup, _ token.Pos, keyword token.Token, iota int) ast.Spec { - if p.trace { - defer un(trace(p, keyword.String()+"Spec")) - } - - pos := p.pos - idents := p.parseIdentList() - typ := p.tryIdentOrType() - var values []ast.Expr - // always permit optional initialization for more tolerant parsing - if p.tok == token.ASSIGN { - p.next() - values = p.parseList(true) - } - p.expectSemi() // call before accessing p.linecomment - - switch keyword { - case token.VAR: - if typ == nil && values == nil { - p.error(pos, "missing variable type or initialization") - } - case token.CONST: - if values == nil && (iota == 0 || typ != nil) { - p.error(pos, "missing constant value") - } - } - - spec := &ast.ValueSpec{ - Doc: doc, - Names: idents, - Type: typ, - Values: values, - Comment: p.lineComment, - } - return spec -} - -func (p *parser) parseGenericType(spec *ast.TypeSpec, openPos token.Pos, name0 *ast.Ident, typ0 ast.Expr) { - if p.trace { - defer un(trace(p, "parseGenericType")) - } - - list := p.parseParameterList(name0, typ0, token.RBRACK) - closePos := p.expect(token.RBRACK) - spec.TypeParams = &ast.FieldList{Opening: openPos, List: list, Closing: closePos} - // Let the type checker decide whether to accept type parameters on aliases: - // see issue #46477. - if p.tok == token.ASSIGN { - // type alias - spec.Assign = p.pos - p.next() - } - spec.Type = p.parseType() -} - -func (p *parser) parseTypeSpec(doc *ast.CommentGroup, _ token.Pos, _ token.Token, _ int) ast.Spec { - if p.trace { - defer un(trace(p, "TypeSpec")) - } - - name := p.parseIdent() - spec := &ast.TypeSpec{Doc: doc, Name: name} - - if p.tok == token.LBRACK && p.allowGenerics() { - // spec.Name "[" ... - // array/slice type or type parameter list - lbrack := p.pos - p.next() - if p.tok == token.IDENT { - // We may have an array type or a type parameter list. - // In either case we expect an expression x (which may - // just be a name, or a more complex expression) which - // we can analyze further. - // - // A type parameter list may have a type bound starting - // with a "[" as in: P []E. In that case, simply parsing - // an expression would lead to an error: P[] is invalid. - // But since index or slice expressions are never constant - // and thus invalid array length expressions, if the name - // is followed by "[" it must be the start of an array or - // slice constraint. Only if we don't see a "[" do we - // need to parse a full expression. Notably, name <- x - // is not a concern because name <- x is a statement and - // not an expression. - var x ast.Expr = p.parseIdent() - if p.tok != token.LBRACK { - // To parse the expression starting with name, expand - // the call sequence we would get by passing in name - // to parser.expr, and pass in name to parsePrimaryExpr. - p.exprLev++ - lhs := p.parsePrimaryExpr(x) - x = p.parseBinaryExpr(lhs, token.LowestPrec+1, false) - p.exprLev-- - } - // Analyze expression x. If we can split x into a type parameter - // name, possibly followed by a type parameter type, we consider - // this the start of a type parameter list, with some caveats: - // a single name followed by "]" tilts the decision towards an - // array declaration; a type parameter type that could also be - // an ordinary expression but which is followed by a comma tilts - // the decision towards a type parameter list. - if pname, ptype := extractName(x, p.tok == token.COMMA); pname != nil && (ptype != nil || p.tok != token.RBRACK) { - // spec.Name "[" pname ... - // spec.Name "[" pname ptype ... - // spec.Name "[" pname ptype "," ... - p.parseGenericType(spec, lbrack, pname, ptype) // ptype may be nil - } else { - // spec.Name "[" pname "]" ... - // spec.Name "[" x ... - spec.Type = p.parseArrayType(lbrack, x) - } - } else { - // array type - spec.Type = p.parseArrayType(lbrack, nil) - } - } else { - // no type parameters - if p.tok == token.ASSIGN { - // type alias - spec.Assign = p.pos - p.next() - } - spec.Type = p.parseType() - } - - p.expectSemi() // call before accessing p.linecomment - spec.Comment = p.lineComment - - return spec -} - -// extractName splits the expression x into (name, expr) if syntactically -// x can be written as name expr. The split only happens if expr is a type -// element (per the isTypeElem predicate) or if force is set. -// If x is just a name, the result is (name, nil). If the split succeeds, -// the result is (name, expr). Otherwise the result is (nil, x). -// Examples: -// -// x force name expr -// ------------------------------------ -// P*[]int T/F P *[]int -// P*E T P *E -// P*E F nil P*E -// P([]int) T/F P []int -// P(E) T P E -// P(E) F nil P(E) -// P*E|F|~G T/F P *E|F|~G -// P*E|F|G T P *E|F|G -// P*E|F|G F nil P*E|F|G -func extractName(x ast.Expr, force bool) (*ast.Ident, ast.Expr) { - switch x := x.(type) { - case *ast.Ident: - return x, nil - case *ast.BinaryExpr: - switch x.Op { - case token.MUL: - if name, _ := x.X.(*ast.Ident); name != nil && (force || isTypeElem(x.Y)) { - // x = name *x.Y - return name, &ast.StarExpr{Star: x.OpPos, X: x.Y} - } - case token.OR: - if name, lhs := extractName(x.X, force || isTypeElem(x.Y)); name != nil && lhs != nil { - // x = name lhs|x.Y - op := *x - op.X = lhs - return name, &op - } - } - case *ast.CallExpr: - if name, _ := x.Fun.(*ast.Ident); name != nil { - if len(x.Args) == 1 && x.Ellipsis == token.NoPos && (force || isTypeElem(x.Args[0])) { - // x = name "(" x.ArgList[0] ")" - return name, x.Args[0] - } - } - } - return nil, x -} - -// isTypeElem reports whether x is a (possibly parenthesized) type element expression. -// The result is false if x could be a type element OR an ordinary (value) expression. -func isTypeElem(x ast.Expr) bool { - switch x := x.(type) { - case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType: - return true - case *ast.BinaryExpr: - return isTypeElem(x.X) || isTypeElem(x.Y) - case *ast.UnaryExpr: - return x.Op == token.TILDE - case *ast.ParenExpr: - return isTypeElem(x.X) - } - return false -} - -func (p *parser) parseGenDecl(keyword token.Token, f parseSpecFunction) *ast.GenDecl { - if p.trace { - defer un(trace(p, "GenDecl("+keyword.String()+")")) - } - - doc := p.leadComment - pos := p.expect(keyword) - var lparen, rparen token.Pos - var list []ast.Spec - if p.tok == token.LPAREN { - lparen = p.pos - p.next() - for iota := 0; p.tok != token.RPAREN && p.tok != token.EOF; iota++ { - list = append(list, f(p.leadComment, pos, keyword, iota)) - } - rparen = p.expect(token.RPAREN) - p.expectSemi() - } else { - list = append(list, f(nil, pos, keyword, 0)) - } - - return &ast.GenDecl{ - Doc: doc, - TokPos: pos, - Tok: keyword, - Lparen: lparen, - Specs: list, - Rparen: rparen, - } -} - -func (p *parser) parseFuncDecl() *ast.FuncDecl { - if p.trace { - defer un(trace(p, "FunctionDecl")) - } - - doc := p.leadComment - pos := p.expect(token.FUNC) - - var recv *ast.FieldList - if p.tok == token.LPAREN { - _, recv = p.parseParameters(false) - } - - ident := p.parseIdent() - - tparams, params := p.parseParameters(true) - if recv != nil && tparams != nil { - // Method declarations do not have type parameters. We parse them for a - // better error message and improved error recovery. - p.error(tparams.Opening, "method must have no type parameters") - tparams = nil - } - results := p.parseResult() - - var body *ast.BlockStmt - switch p.tok { - case token.LBRACE: - body = p.parseBody() - p.expectSemi() - case token.SEMICOLON: - p.next() - if p.tok == token.LBRACE { - // opening { of function declaration on next line - p.error(p.pos, "unexpected semicolon or newline before {") - body = p.parseBody() - p.expectSemi() - } - default: - p.expectSemi() - } - - decl := &ast.FuncDecl{ - Doc: doc, - Recv: recv, - Name: ident, - Type: &ast.FuncType{ - Func: pos, - TypeParams: tparams, - Params: params, - Results: results, - }, - Body: body, - } - return decl -} - -func (p *parser) parseDecl(sync map[token.Token]bool) ast.Decl { - if p.trace { - defer un(trace(p, "Declaration")) - } - - var f parseSpecFunction - switch p.tok { - case token.CONST, token.VAR: - f = p.parseValueSpec - - case token.TYPE: - f = p.parseTypeSpec - - case token.FUNC: - return p.parseFuncDecl() - - default: - pos := p.pos - p.errorExpected(pos, "declaration") - p.advance(sync) - return &ast.BadDecl{From: pos, To: p.pos} - } - - return p.parseGenDecl(p.tok, f) -} - -// ---------------------------------------------------------------------------- -// Source files - -func (p *parser) parseFile() *ast.File { - if p.trace { - defer un(trace(p, "File")) - } - - // Don't bother parsing the rest if we had errors scanning the first token. - // Likely not a Go source file at all. - if p.errors.Len() != 0 { - return nil - } - - // package clause - doc := p.leadComment - pos := p.expect(token.PACKAGE) - // Go spec: The package clause is not a declaration; - // the package name does not appear in any scope. - ident := p.parseIdent() - if ident.Name == "_" && p.mode&DeclarationErrors != 0 { - p.error(p.pos, "invalid package name _") - } - p.expectSemi() - - // Don't bother parsing the rest if we had errors parsing the package clause. - // Likely not a Go source file at all. - if p.errors.Len() != 0 { - return nil - } - - var decls []ast.Decl - if p.mode&PackageClauseOnly == 0 { - // import decls - for p.tok == token.IMPORT { - decls = append(decls, p.parseGenDecl(token.IMPORT, p.parseImportSpec)) - } - - if p.mode&ImportsOnly == 0 { - // rest of package body - for p.tok != token.EOF { - decls = append(decls, p.parseDecl(declStart)) - } - } - } - - f := &ast.File{ - Doc: doc, - Package: pos, - Name: ident, - Decls: decls, - Imports: p.imports, - Comments: p.comments, - } - var declErr func(token.Pos, string) - if p.mode&DeclarationErrors != 0 { - declErr = p.error - } - if p.mode&SkipObjectResolution == 0 { - resolveFile(f, p.file, declErr) - } - - return f -} diff --git a/internal/backport/go/parser/parser_test.go b/internal/backport/go/parser/parser_test.go deleted file mode 100644 index feb05ea41c..0000000000 --- a/internal/backport/go/parser/parser_test.go +++ /dev/null @@ -1,579 +0,0 @@ -// Copyright 2009 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 parser - -import ( - "bytes" - "fmt" - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/token" - "io/fs" - "strings" - "testing" -) - -var validFiles = []string{ - "parser.go", - "parser_test.go", - "error_test.go", - "short_test.go", -} - -func TestParse(t *testing.T) { - for _, filename := range validFiles { - _, err := ParseFile(token.NewFileSet(), filename, nil, DeclarationErrors) - if err != nil { - t.Fatalf("ParseFile(%s): %v", filename, err) - } - } -} - -func nameFilter(filename string) bool { - switch filename { - case "parser.go", "interface.go", "parser_test.go": - return true - case "parser.go.orig": - return true // permit but should be ignored by ParseDir - } - return false -} - -func dirFilter(f fs.FileInfo) bool { return nameFilter(f.Name()) } - -func TestParseFile(t *testing.T) { - src := "package p\nvar _=s[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]" - _, err := ParseFile(token.NewFileSet(), "", src, 0) - if err == nil { - t.Errorf("ParseFile(%s) succeeded unexpectedly", src) - } -} - -func TestParseExprFrom(t *testing.T) { - src := "s[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]" - _, err := ParseExprFrom(token.NewFileSet(), "", src, 0) - if err == nil { - t.Errorf("ParseExprFrom(%s) succeeded unexpectedly", src) - } -} - -func TestParseDir(t *testing.T) { - path := "." - pkgs, err := ParseDir(token.NewFileSet(), path, dirFilter, 0) - if err != nil { - t.Fatalf("ParseDir(%s): %v", path, err) - } - if n := len(pkgs); n != 1 { - t.Errorf("got %d packages; want 1", n) - } - pkg := pkgs["parser"] - if pkg == nil { - t.Errorf(`package "parser" not found`) - return - } - if n := len(pkg.Files); n != 3 { - t.Errorf("got %d package files; want 3", n) - } - for filename := range pkg.Files { - if !nameFilter(filename) { - t.Errorf("unexpected package file: %s", filename) - } - } -} - -func TestIssue42951(t *testing.T) { - path := "./testdata/issue42951" - _, err := ParseDir(token.NewFileSet(), path, nil, 0) - if err != nil { - t.Errorf("ParseDir(%s): %v", path, err) - } -} - -func TestParseExpr(t *testing.T) { - // just kicking the tires: - // a valid arithmetic expression - src := "a + b" - x, err := ParseExpr(src) - if err != nil { - t.Errorf("ParseExpr(%q): %v", src, err) - } - // sanity check - if _, ok := x.(*ast.BinaryExpr); !ok { - t.Errorf("ParseExpr(%q): got %T, want *ast.BinaryExpr", src, x) - } - - // a valid type expression - src = "struct{x *int}" - x, err = ParseExpr(src) - if err != nil { - t.Errorf("ParseExpr(%q): %v", src, err) - } - // sanity check - if _, ok := x.(*ast.StructType); !ok { - t.Errorf("ParseExpr(%q): got %T, want *ast.StructType", src, x) - } - - // an invalid expression - src = "a + *" - x, err = ParseExpr(src) - if err == nil { - t.Errorf("ParseExpr(%q): got no error", src) - } - if x == nil { - t.Errorf("ParseExpr(%q): got no (partial) result", src) - } - if _, ok := x.(*ast.BinaryExpr); !ok { - t.Errorf("ParseExpr(%q): got %T, want *ast.BinaryExpr", src, x) - } - - // a valid expression followed by extra tokens is invalid - src = "a[i] := x" - if _, err := ParseExpr(src); err == nil { - t.Errorf("ParseExpr(%q): got no error", src) - } - - // a semicolon is not permitted unless automatically inserted - src = "a + b\n" - if _, err := ParseExpr(src); err != nil { - t.Errorf("ParseExpr(%q): got error %s", src, err) - } - src = "a + b;" - if _, err := ParseExpr(src); err == nil { - t.Errorf("ParseExpr(%q): got no error", src) - } - - // various other stuff following a valid expression - const validExpr = "a + b" - const anything = "dh3*#D)#_" - for _, c := range "!)]};," { - src := validExpr + string(c) + anything - if _, err := ParseExpr(src); err == nil { - t.Errorf("ParseExpr(%q): got no error", src) - } - } - - // ParseExpr must not crash - for _, src := range valids { - ParseExpr(src) - } -} - -func TestColonEqualsScope(t *testing.T) { - f, err := ParseFile(token.NewFileSet(), "", `package p; func f() { x, y, z := x, y, z }`, 0) - if err != nil { - t.Fatal(err) - } - - // RHS refers to undefined globals; LHS does not. - as := f.Decls[0].(*ast.FuncDecl).Body.List[0].(*ast.AssignStmt) - for _, v := range as.Rhs { - id := v.(*ast.Ident) - if id.Obj != nil { - t.Errorf("rhs %s has Obj, should not", id.Name) - } - } - for _, v := range as.Lhs { - id := v.(*ast.Ident) - if id.Obj == nil { - t.Errorf("lhs %s does not have Obj, should", id.Name) - } - } -} - -func TestVarScope(t *testing.T) { - f, err := ParseFile(token.NewFileSet(), "", `package p; func f() { var x, y, z = x, y, z }`, 0) - if err != nil { - t.Fatal(err) - } - - // RHS refers to undefined globals; LHS does not. - as := f.Decls[0].(*ast.FuncDecl).Body.List[0].(*ast.DeclStmt).Decl.(*ast.GenDecl).Specs[0].(*ast.ValueSpec) - for _, v := range as.Values { - id := v.(*ast.Ident) - if id.Obj != nil { - t.Errorf("rhs %s has Obj, should not", id.Name) - } - } - for _, id := range as.Names { - if id.Obj == nil { - t.Errorf("lhs %s does not have Obj, should", id.Name) - } - } -} - -func TestObjects(t *testing.T) { - const src = ` -package p -import fmt "fmt" -const pi = 3.14 -type T struct{} -var x int -func f() { L: } -` - - f, err := ParseFile(token.NewFileSet(), "", src, 0) - if err != nil { - t.Fatal(err) - } - - objects := map[string]ast.ObjKind{ - "p": ast.Bad, // not in a scope - "fmt": ast.Bad, // not resolved yet - "pi": ast.Con, - "T": ast.Typ, - "x": ast.Var, - "int": ast.Bad, // not resolved yet - "f": ast.Fun, - "L": ast.Lbl, - } - - ast.Inspect(f, func(n ast.Node) bool { - if ident, ok := n.(*ast.Ident); ok { - obj := ident.Obj - if obj == nil { - if objects[ident.Name] != ast.Bad { - t.Errorf("no object for %s", ident.Name) - } - return true - } - if obj.Name != ident.Name { - t.Errorf("names don't match: obj.Name = %s, ident.Name = %s", obj.Name, ident.Name) - } - kind := objects[ident.Name] - if obj.Kind != kind { - t.Errorf("%s: obj.Kind = %s; want %s", ident.Name, obj.Kind, kind) - } - } - return true - }) -} - -func TestUnresolved(t *testing.T) { - f, err := ParseFile(token.NewFileSet(), "", ` -package p -// -func f1a(int) -func f2a(byte, int, float) -func f3a(a, b int, c float) -func f4a(...complex) -func f5a(a s1a, b ...complex) -// -func f1b(*int) -func f2b([]byte, (int), *float) -func f3b(a, b *int, c []float) -func f4b(...*complex) -func f5b(a s1a, b ...[]complex) -// -type s1a struct { int } -type s2a struct { byte; int; s1a } -type s3a struct { a, b int; c float } -// -type s1b struct { *int } -type s2b struct { byte; int; *float } -type s3b struct { a, b *s3b; c []float } -`, 0) - if err != nil { - t.Fatal(err) - } - - want := "int " + // f1a - "byte int float " + // f2a - "int float " + // f3a - "complex " + // f4a - "complex " + // f5a - // - "int " + // f1b - "byte int float " + // f2b - "int float " + // f3b - "complex " + // f4b - "complex " + // f5b - // - "int " + // s1a - "byte int " + // s2a - "int float " + // s3a - // - "int " + // s1a - "byte int float " + // s2a - "float " // s3a - - // collect unresolved identifiers - var buf bytes.Buffer - for _, u := range f.Unresolved { - buf.WriteString(u.Name) - buf.WriteByte(' ') - } - got := buf.String() - - if got != want { - t.Errorf("\ngot: %s\nwant: %s", got, want) - } -} - -var imports = map[string]bool{ - `"a"`: true, - "`a`": true, - `"a/b"`: true, - `"a.b"`: true, - `"m\x61th"`: true, - `"greek/αβ"`: true, - `""`: false, - - // Each of these pairs tests both `` vs "" strings - // and also use of invalid characters spelled out as - // escape sequences and written directly. - // For example `"\x00"` tests import "\x00" - // while "`\x00`" tests import ``. - `"\x00"`: false, - "`\x00`": false, - `"\x7f"`: false, - "`\x7f`": false, - `"a!"`: false, - "`a!`": false, - `"a b"`: false, - "`a b`": false, - `"a\\b"`: false, - "`a\\b`": false, - "\"`a`\"": false, - "`\"a\"`": false, - `"\x80\x80"`: false, - "`\x80\x80`": false, - `"\xFFFD"`: false, - "`\xFFFD`": false, -} - -func TestImports(t *testing.T) { - for path, isValid := range imports { - src := fmt.Sprintf("package p; import %s", path) - _, err := ParseFile(token.NewFileSet(), "", src, 0) - switch { - case err != nil && isValid: - t.Errorf("ParseFile(%s): got %v; expected no error", src, err) - case err == nil && !isValid: - t.Errorf("ParseFile(%s): got no error; expected one", src) - } - } -} - -func TestCommentGroups(t *testing.T) { - f, err := ParseFile(token.NewFileSet(), "", ` -package p /* 1a */ /* 1b */ /* 1c */ // 1d -/* 2a -*/ -// 2b -const pi = 3.1415 -/* 3a */ // 3b -/* 3c */ const e = 2.7182 - -// Example from issue 3139 -func ExampleCount() { - fmt.Println(strings.Count("cheese", "e")) - fmt.Println(strings.Count("five", "")) // before & after each rune - // Output: - // 3 - // 5 -} -`, ParseComments) - if err != nil { - t.Fatal(err) - } - expected := [][]string{ - {"/* 1a */", "/* 1b */", "/* 1c */", "// 1d"}, - {"/* 2a\n*/", "// 2b"}, - {"/* 3a */", "// 3b", "/* 3c */"}, - {"// Example from issue 3139"}, - {"// before & after each rune"}, - {"// Output:", "// 3", "// 5"}, - } - if len(f.Comments) != len(expected) { - t.Fatalf("got %d comment groups; expected %d", len(f.Comments), len(expected)) - } - for i, exp := range expected { - got := f.Comments[i].List - if len(got) != len(exp) { - t.Errorf("got %d comments in group %d; expected %d", len(got), i, len(exp)) - continue - } - for j, exp := range exp { - got := got[j].Text - if got != exp { - t.Errorf("got %q in group %d; expected %q", got, i, exp) - } - } - } -} - -func getField(file *ast.File, fieldname string) *ast.Field { - parts := strings.Split(fieldname, ".") - for _, d := range file.Decls { - if d, ok := d.(*ast.GenDecl); ok && d.Tok == token.TYPE { - for _, s := range d.Specs { - if s, ok := s.(*ast.TypeSpec); ok && s.Name.Name == parts[0] { - if s, ok := s.Type.(*ast.StructType); ok { - for _, f := range s.Fields.List { - for _, name := range f.Names { - if name.Name == parts[1] { - return f - } - } - } - } - } - } - } - } - return nil -} - -// Don't use ast.CommentGroup.Text() - we want to see exact comment text. -func commentText(c *ast.CommentGroup) string { - var buf bytes.Buffer - if c != nil { - for _, c := range c.List { - buf.WriteString(c.Text) - } - } - return buf.String() -} - -func checkFieldComments(t *testing.T, file *ast.File, fieldname, lead, line string) { - f := getField(file, fieldname) - if f == nil { - t.Fatalf("field not found: %s", fieldname) - } - if got := commentText(f.Doc); got != lead { - t.Errorf("got lead comment %q; expected %q", got, lead) - } - if got := commentText(f.Comment); got != line { - t.Errorf("got line comment %q; expected %q", got, line) - } -} - -func TestLeadAndLineComments(t *testing.T) { - f, err := ParseFile(token.NewFileSet(), "", ` -package p -type T struct { - /* F1 lead comment */ - // - F1 int /* F1 */ // line comment - // F2 lead - // comment - F2 int // F2 line comment - // f3 lead comment - f3 int // f3 line comment -} -`, ParseComments) - if err != nil { - t.Fatal(err) - } - checkFieldComments(t, f, "T.F1", "/* F1 lead comment *///", "/* F1 */// line comment") - checkFieldComments(t, f, "T.F2", "// F2 lead// comment", "// F2 line comment") - checkFieldComments(t, f, "T.f3", "// f3 lead comment", "// f3 line comment") - ast.FileExports(f) - checkFieldComments(t, f, "T.F1", "/* F1 lead comment *///", "/* F1 */// line comment") - checkFieldComments(t, f, "T.F2", "// F2 lead// comment", "// F2 line comment") - if getField(f, "T.f3") != nil { - t.Error("not expected to find T.f3") - } -} - -// TestIssue9979 verifies that empty statements are contained within their enclosing blocks. -func TestIssue9979(t *testing.T) { - for _, src := range []string{ - "package p; func f() {;}", - "package p; func f() {L:}", - "package p; func f() {L:;}", - "package p; func f() {L:\n}", - "package p; func f() {L:\n;}", - "package p; func f() { ; }", - "package p; func f() { L: }", - "package p; func f() { L: ; }", - "package p; func f() { L: \n}", - "package p; func f() { L: \n; }", - } { - fset := token.NewFileSet() - f, err := ParseFile(fset, "", src, 0) - if err != nil { - t.Fatal(err) - } - - var pos, end token.Pos - ast.Inspect(f, func(x ast.Node) bool { - switch s := x.(type) { - case *ast.BlockStmt: - pos, end = s.Pos()+1, s.End()-1 // exclude "{", "}" - case *ast.LabeledStmt: - pos, end = s.Pos()+2, s.End() // exclude "L:" - case *ast.EmptyStmt: - // check containment - if s.Pos() < pos || s.End() > end { - t.Errorf("%s: %T[%d, %d] not inside [%d, %d]", src, s, s.Pos(), s.End(), pos, end) - } - // check semicolon - offs := fset.Position(s.Pos()).Offset - if ch := src[offs]; ch != ';' != s.Implicit { - want := "want ';'" - if s.Implicit { - want = "but ';' is implicit" - } - t.Errorf("%s: found %q at offset %d; %s", src, ch, offs, want) - } - } - return true - }) - } -} - -// TestIncompleteSelection ensures that an incomplete selector -// expression is parsed as a (blank) *ast.SelectorExpr, not a -// *ast.BadExpr. -func TestIncompleteSelection(t *testing.T) { - for _, src := range []string{ - "package p; var _ = fmt.", // at EOF - "package p; var _ = fmt.\ntype X int", // not at EOF - } { - fset := token.NewFileSet() - f, err := ParseFile(fset, "", src, 0) - if err == nil { - t.Errorf("ParseFile(%s) succeeded unexpectedly", src) - continue - } - - const wantErr = "expected selector or type assertion" - if !strings.Contains(err.Error(), wantErr) { - t.Errorf("ParseFile returned wrong error %q, want %q", err, wantErr) - } - - var sel *ast.SelectorExpr - ast.Inspect(f, func(n ast.Node) bool { - if n, ok := n.(*ast.SelectorExpr); ok { - sel = n - } - return true - }) - if sel == nil { - t.Error("found no *ast.SelectorExpr") - continue - } - const wantSel = "&{fmt _}" - if fmt.Sprint(sel) != wantSel { - t.Errorf("found selector %s, want %s", sel, wantSel) - continue - } - } -} - -func TestLastLineComment(t *testing.T) { - const src = `package main -type x int // comment -` - fset := token.NewFileSet() - f, err := ParseFile(fset, "", src, ParseComments) - if err != nil { - t.Fatal(err) - } - comment := f.Decls[0].(*ast.GenDecl).Specs[0].(*ast.TypeSpec).Comment.List[0].Text - if comment != "// comment" { - t.Errorf("got %q, want %q", comment, "// comment") - } -} diff --git a/internal/backport/go/parser/performance_test.go b/internal/backport/go/parser/performance_test.go deleted file mode 100644 index 8b219f162a..0000000000 --- a/internal/backport/go/parser/performance_test.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2012 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 parser - -import ( - "os" - "testing" - - "golang.org/x/website/internal/backport/go/token" -) - -// TODO(rfindley): use a testdata file or file from another package here, to -// avoid a moving target. -var src = readFile("parser.go") - -func readFile(filename string) []byte { - data, err := os.ReadFile(filename) - if err != nil { - panic(err) - } - return data -} - -func BenchmarkParse(b *testing.B) { - b.SetBytes(int64(len(src))) - for i := 0; i < b.N; i++ { - if _, err := ParseFile(token.NewFileSet(), "", src, ParseComments); err != nil { - b.Fatalf("benchmark failed due to parse error: %s", err) - } - } -} - -func BenchmarkParseOnly(b *testing.B) { - b.SetBytes(int64(len(src))) - for i := 0; i < b.N; i++ { - if _, err := ParseFile(token.NewFileSet(), "", src, ParseComments|SkipObjectResolution); err != nil { - b.Fatalf("benchmark failed due to parse error: %s", err) - } - } -} - -func BenchmarkResolve(b *testing.B) { - b.SetBytes(int64(len(src))) - for i := 0; i < b.N; i++ { - b.StopTimer() - fset := token.NewFileSet() - file, err := ParseFile(fset, "", src, SkipObjectResolution) - if err != nil { - b.Fatalf("benchmark failed due to parse error: %s", err) - } - b.StartTimer() - handle := fset.File(file.Package) - resolveFile(file, handle, nil) - } -} diff --git a/internal/backport/go/parser/resolver.go b/internal/backport/go/parser/resolver.go deleted file mode 100644 index b648836318..0000000000 --- a/internal/backport/go/parser/resolver.go +++ /dev/null @@ -1,607 +0,0 @@ -// Copyright 2021 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 parser - -import ( - "fmt" - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/token" - "strings" -) - -const debugResolve = false - -// resolveFile walks the given file to resolve identifiers within the file -// scope, updating ast.Ident.Obj fields with declaration information. -// -// If declErr is non-nil, it is used to report declaration errors during -// resolution. tok is used to format position in error messages. -func resolveFile(file *ast.File, handle *token.File, declErr func(token.Pos, string)) { - pkgScope := ast.NewScope(nil) - r := &resolver{ - handle: handle, - declErr: declErr, - topScope: pkgScope, - pkgScope: pkgScope, - depth: 1, - } - - for _, decl := range file.Decls { - ast.Walk(r, decl) - } - - r.closeScope() - assert(r.topScope == nil, "unbalanced scopes") - assert(r.labelScope == nil, "unbalanced label scopes") - - // resolve global identifiers within the same file - i := 0 - for _, ident := range r.unresolved { - // i <= index for current ident - assert(ident.Obj == unresolved, "object already resolved") - ident.Obj = r.pkgScope.Lookup(ident.Name) // also removes unresolved sentinel - if ident.Obj == nil { - r.unresolved[i] = ident - i++ - } else if debugResolve { - pos := ident.Obj.Decl.(interface{ Pos() token.Pos }).Pos() - r.trace("resolved %s@%v to package object %v", ident.Name, ident.Pos(), pos) - } - } - file.Scope = r.pkgScope - file.Unresolved = r.unresolved[0:i] -} - -type resolver struct { - handle *token.File - declErr func(token.Pos, string) - - // Ordinary identifier scopes - pkgScope *ast.Scope // pkgScope.Outer == nil - topScope *ast.Scope // top-most scope; may be pkgScope - unresolved []*ast.Ident // unresolved identifiers - depth int // scope depth - - // Label scopes - // (maintained by open/close LabelScope) - labelScope *ast.Scope // label scope for current function - targetStack [][]*ast.Ident // stack of unresolved labels -} - -func (r *resolver) trace(format string, args ...interface{}) { - fmt.Println(strings.Repeat(". ", r.depth) + r.sprintf(format, args...)) -} - -func (r *resolver) sprintf(format string, args ...interface{}) string { - for i, arg := range args { - switch arg := arg.(type) { - case token.Pos: - args[i] = r.handle.Position(arg) - } - } - return fmt.Sprintf(format, args...) -} - -func (r *resolver) openScope(pos token.Pos) { - if debugResolve { - r.trace("opening scope @%v", pos) - r.depth++ - } - r.topScope = ast.NewScope(r.topScope) -} - -func (r *resolver) closeScope() { - if debugResolve { - r.depth-- - r.trace("closing scope") - } - r.topScope = r.topScope.Outer -} - -func (r *resolver) openLabelScope() { - r.labelScope = ast.NewScope(r.labelScope) - r.targetStack = append(r.targetStack, nil) -} - -func (r *resolver) closeLabelScope() { - // resolve labels - n := len(r.targetStack) - 1 - scope := r.labelScope - for _, ident := range r.targetStack[n] { - ident.Obj = scope.Lookup(ident.Name) - if ident.Obj == nil && r.declErr != nil { - r.declErr(ident.Pos(), fmt.Sprintf("label %s undefined", ident.Name)) - } - } - // pop label scope - r.targetStack = r.targetStack[0:n] - r.labelScope = r.labelScope.Outer -} - -func (r *resolver) declare(decl, data interface{}, scope *ast.Scope, kind ast.ObjKind, idents ...*ast.Ident) { - for _, ident := range idents { - if ident.Obj != nil { - panic(fmt.Sprintf("%v: identifier %s already declared or resolved", ident.Pos(), ident.Name)) - } - obj := ast.NewObj(kind, ident.Name) - // remember the corresponding declaration for redeclaration - // errors and global variable resolution/typechecking phase - obj.Decl = decl - obj.Data = data - // Identifiers (for receiver type parameters) are written to the scope, but - // never set as the resolved object. See issue #50956. - if _, ok := decl.(*ast.Ident); !ok { - ident.Obj = obj - } - if ident.Name != "_" { - if debugResolve { - r.trace("declaring %s@%v", ident.Name, ident.Pos()) - } - if alt := scope.Insert(obj); alt != nil && r.declErr != nil { - prevDecl := "" - if pos := alt.Pos(); pos.IsValid() { - prevDecl = r.sprintf("\n\tprevious declaration at %v", pos) - } - r.declErr(ident.Pos(), fmt.Sprintf("%s redeclared in this block%s", ident.Name, prevDecl)) - } - } - } -} - -func (r *resolver) shortVarDecl(decl *ast.AssignStmt) { - // Go spec: A short variable declaration may redeclare variables - // provided they were originally declared in the same block with - // the same type, and at least one of the non-blank variables is new. - n := 0 // number of new variables - for _, x := range decl.Lhs { - if ident, isIdent := x.(*ast.Ident); isIdent { - assert(ident.Obj == nil, "identifier already declared or resolved") - obj := ast.NewObj(ast.Var, ident.Name) - // remember corresponding assignment for other tools - obj.Decl = decl - ident.Obj = obj - if ident.Name != "_" { - if debugResolve { - r.trace("declaring %s@%v", ident.Name, ident.Pos()) - } - if alt := r.topScope.Insert(obj); alt != nil { - ident.Obj = alt // redeclaration - } else { - n++ // new declaration - } - } - } - } - if n == 0 && r.declErr != nil { - r.declErr(decl.Lhs[0].Pos(), "no new variables on left side of :=") - } -} - -// The unresolved object is a sentinel to mark identifiers that have been added -// to the list of unresolved identifiers. The sentinel is only used for verifying -// internal consistency. -var unresolved = new(ast.Object) - -// If x is an identifier, resolve attempts to resolve x by looking up -// the object it denotes. If no object is found and collectUnresolved is -// set, x is marked as unresolved and collected in the list of unresolved -// identifiers. -func (r *resolver) resolve(ident *ast.Ident, collectUnresolved bool) { - if ident.Obj != nil { - panic(r.sprintf("%v: identifier %s already declared or resolved", ident.Pos(), ident.Name)) - } - // '_' should never refer to existing declarations, because it has special - // handling in the spec. - if ident.Name == "_" { - return - } - for s := r.topScope; s != nil; s = s.Outer { - if obj := s.Lookup(ident.Name); obj != nil { - if debugResolve { - r.trace("resolved %v:%s to %v", ident.Pos(), ident.Name, obj) - } - assert(obj.Name != "", "obj with no name") - // Identifiers (for receiver type parameters) are written to the scope, - // but never set as the resolved object. See issue #50956. - if _, ok := obj.Decl.(*ast.Ident); !ok { - ident.Obj = obj - } - return - } - } - // all local scopes are known, so any unresolved identifier - // must be found either in the file scope, package scope - // (perhaps in another file), or universe scope --- collect - // them so that they can be resolved later - if collectUnresolved { - ident.Obj = unresolved - r.unresolved = append(r.unresolved, ident) - } -} - -func (r *resolver) walkExprs(list []ast.Expr) { - for _, node := range list { - ast.Walk(r, node) - } -} - -func (r *resolver) walkLHS(list []ast.Expr) { - for _, expr := range list { - expr := unparen(expr) - if _, ok := expr.(*ast.Ident); !ok && expr != nil { - ast.Walk(r, expr) - } - } -} - -func (r *resolver) walkStmts(list []ast.Stmt) { - for _, stmt := range list { - ast.Walk(r, stmt) - } -} - -func (r *resolver) Visit(node ast.Node) ast.Visitor { - if debugResolve && node != nil { - r.trace("node %T@%v", node, node.Pos()) - } - - switch n := node.(type) { - - // Expressions. - case *ast.Ident: - r.resolve(n, true) - - case *ast.FuncLit: - r.openScope(n.Pos()) - defer r.closeScope() - r.walkFuncType(n.Type) - r.walkBody(n.Body) - - case *ast.SelectorExpr: - ast.Walk(r, n.X) - // Note: don't try to resolve n.Sel, as we don't support qualified - // resolution. - - case *ast.StructType: - r.openScope(n.Pos()) - defer r.closeScope() - r.walkFieldList(n.Fields, ast.Var) - - case *ast.FuncType: - r.openScope(n.Pos()) - defer r.closeScope() - r.walkFuncType(n) - - case *ast.CompositeLit: - if n.Type != nil { - ast.Walk(r, n.Type) - } - for _, e := range n.Elts { - if kv, _ := e.(*ast.KeyValueExpr); kv != nil { - // See issue #45160: try to resolve composite lit keys, but don't - // collect them as unresolved if resolution failed. This replicates - // existing behavior when resolving during parsing. - if ident, _ := kv.Key.(*ast.Ident); ident != nil { - r.resolve(ident, false) - } else { - ast.Walk(r, kv.Key) - } - ast.Walk(r, kv.Value) - } else { - ast.Walk(r, e) - } - } - - case *ast.InterfaceType: - r.openScope(n.Pos()) - defer r.closeScope() - r.walkFieldList(n.Methods, ast.Fun) - - // Statements - case *ast.LabeledStmt: - r.declare(n, nil, r.labelScope, ast.Lbl, n.Label) - ast.Walk(r, n.Stmt) - - case *ast.AssignStmt: - r.walkExprs(n.Rhs) - if n.Tok == token.DEFINE { - r.shortVarDecl(n) - } else { - r.walkExprs(n.Lhs) - } - - case *ast.BranchStmt: - // add to list of unresolved targets - if n.Tok != token.FALLTHROUGH && n.Label != nil { - depth := len(r.targetStack) - 1 - r.targetStack[depth] = append(r.targetStack[depth], n.Label) - } - - case *ast.BlockStmt: - r.openScope(n.Pos()) - defer r.closeScope() - r.walkStmts(n.List) - - case *ast.IfStmt: - r.openScope(n.Pos()) - defer r.closeScope() - if n.Init != nil { - ast.Walk(r, n.Init) - } - ast.Walk(r, n.Cond) - ast.Walk(r, n.Body) - if n.Else != nil { - ast.Walk(r, n.Else) - } - - case *ast.CaseClause: - r.walkExprs(n.List) - r.openScope(n.Pos()) - defer r.closeScope() - r.walkStmts(n.Body) - - case *ast.SwitchStmt: - r.openScope(n.Pos()) - defer r.closeScope() - if n.Init != nil { - ast.Walk(r, n.Init) - } - if n.Tag != nil { - // The scope below reproduces some unnecessary behavior of the parser, - // opening an extra scope in case this is a type switch. It's not needed - // for expression switches. - // TODO: remove this once we've matched the parser resolution exactly. - if n.Init != nil { - r.openScope(n.Tag.Pos()) - defer r.closeScope() - } - ast.Walk(r, n.Tag) - } - if n.Body != nil { - r.walkStmts(n.Body.List) - } - - case *ast.TypeSwitchStmt: - if n.Init != nil { - r.openScope(n.Pos()) - defer r.closeScope() - ast.Walk(r, n.Init) - } - r.openScope(n.Assign.Pos()) - defer r.closeScope() - ast.Walk(r, n.Assign) - // s.Body consists only of case clauses, so does not get its own - // scope. - if n.Body != nil { - r.walkStmts(n.Body.List) - } - - case *ast.CommClause: - r.openScope(n.Pos()) - defer r.closeScope() - if n.Comm != nil { - ast.Walk(r, n.Comm) - } - r.walkStmts(n.Body) - - case *ast.SelectStmt: - // as for switch statements, select statement bodies don't get their own - // scope. - if n.Body != nil { - r.walkStmts(n.Body.List) - } - - case *ast.ForStmt: - r.openScope(n.Pos()) - defer r.closeScope() - if n.Init != nil { - ast.Walk(r, n.Init) - } - if n.Cond != nil { - ast.Walk(r, n.Cond) - } - if n.Post != nil { - ast.Walk(r, n.Post) - } - ast.Walk(r, n.Body) - - case *ast.RangeStmt: - r.openScope(n.Pos()) - defer r.closeScope() - ast.Walk(r, n.X) - var lhs []ast.Expr - if n.Key != nil { - lhs = append(lhs, n.Key) - } - if n.Value != nil { - lhs = append(lhs, n.Value) - } - if len(lhs) > 0 { - if n.Tok == token.DEFINE { - // Note: we can't exactly match the behavior of object resolution - // during the parsing pass here, as it uses the position of the RANGE - // token for the RHS OpPos. That information is not contained within - // the AST. - as := &ast.AssignStmt{ - Lhs: lhs, - Tok: token.DEFINE, - TokPos: n.TokPos, - Rhs: []ast.Expr{&ast.UnaryExpr{Op: token.RANGE, X: n.X}}, - } - // TODO(rFindley): this walkLHS reproduced the parser resolution, but - // is it necessary? By comparison, for a normal AssignStmt we don't - // walk the LHS in case there is an invalid identifier list. - r.walkLHS(lhs) - r.shortVarDecl(as) - } else { - r.walkExprs(lhs) - } - } - ast.Walk(r, n.Body) - - // Declarations - case *ast.GenDecl: - switch n.Tok { - case token.CONST, token.VAR: - for i, spec := range n.Specs { - spec := spec.(*ast.ValueSpec) - kind := ast.Con - if n.Tok == token.VAR { - kind = ast.Var - } - r.walkExprs(spec.Values) - if spec.Type != nil { - ast.Walk(r, spec.Type) - } - r.declare(spec, i, r.topScope, kind, spec.Names...) - } - case token.TYPE: - for _, spec := range n.Specs { - spec := spec.(*ast.TypeSpec) - // Go spec: The scope of a type identifier declared inside a function begins - // at the identifier in the TypeSpec and ends at the end of the innermost - // containing block. - r.declare(spec, nil, r.topScope, ast.Typ, spec.Name) - if spec.TypeParams != nil { - r.openScope(spec.Pos()) - defer r.closeScope() - r.walkTParams(spec.TypeParams) - } - ast.Walk(r, spec.Type) - } - } - - case *ast.FuncDecl: - // Open the function scope. - r.openScope(n.Pos()) - defer r.closeScope() - - r.walkRecv(n.Recv) - - // Type parameters are walked normally: they can reference each other, and - // can be referenced by normal parameters. - if n.Type.TypeParams != nil { - r.walkTParams(n.Type.TypeParams) - // TODO(rFindley): need to address receiver type parameters. - } - - // Resolve and declare parameters in a specific order to get duplicate - // declaration errors in the correct location. - r.resolveList(n.Type.Params) - r.resolveList(n.Type.Results) - r.declareList(n.Recv, ast.Var) - r.declareList(n.Type.Params, ast.Var) - r.declareList(n.Type.Results, ast.Var) - - r.walkBody(n.Body) - if n.Recv == nil && n.Name.Name != "init" { - r.declare(n, nil, r.pkgScope, ast.Fun, n.Name) - } - - default: - return r - } - - return nil -} - -func (r *resolver) walkFuncType(typ *ast.FuncType) { - // typ.TypeParams must be walked separately for FuncDecls. - r.resolveList(typ.Params) - r.resolveList(typ.Results) - r.declareList(typ.Params, ast.Var) - r.declareList(typ.Results, ast.Var) -} - -func (r *resolver) resolveList(list *ast.FieldList) { - if list == nil { - return - } - for _, f := range list.List { - if f.Type != nil { - ast.Walk(r, f.Type) - } - } -} - -func (r *resolver) declareList(list *ast.FieldList, kind ast.ObjKind) { - if list == nil { - return - } - for _, f := range list.List { - r.declare(f, nil, r.topScope, kind, f.Names...) - } -} - -func (r *resolver) walkRecv(recv *ast.FieldList) { - // If our receiver has receiver type parameters, we must declare them before - // trying to resolve the rest of the receiver, and avoid re-resolving the - // type parameter identifiers. - if recv == nil || len(recv.List) == 0 { - return // nothing to do - } - typ := recv.List[0].Type - if ptr, ok := typ.(*ast.StarExpr); ok { - typ = ptr.X - } - - var declareExprs []ast.Expr // exprs to declare - var resolveExprs []ast.Expr // exprs to resolve - switch typ := typ.(type) { - case *ast.IndexExpr: - declareExprs = []ast.Expr{typ.Index} - resolveExprs = append(resolveExprs, typ.X) - case *ast.IndexListExpr: - declareExprs = typ.Indices - resolveExprs = append(resolveExprs, typ.X) - default: - resolveExprs = append(resolveExprs, typ) - } - for _, expr := range declareExprs { - if id, _ := expr.(*ast.Ident); id != nil { - r.declare(expr, nil, r.topScope, ast.Typ, id) - } else { - // The receiver type parameter expression is invalid, but try to resolve - // it anyway for consistency. - resolveExprs = append(resolveExprs, expr) - } - } - for _, expr := range resolveExprs { - if expr != nil { - ast.Walk(r, expr) - } - } - // The receiver is invalid, but try to resolve it anyway for consistency. - for _, f := range recv.List[1:] { - if f.Type != nil { - ast.Walk(r, f.Type) - } - } -} - -func (r *resolver) walkFieldList(list *ast.FieldList, kind ast.ObjKind) { - if list == nil { - return - } - r.resolveList(list) - r.declareList(list, kind) -} - -// walkTParams is like walkFieldList, but declares type parameters eagerly so -// that they may be resolved in the constraint expressions held in the field -// Type. -func (r *resolver) walkTParams(list *ast.FieldList) { - r.declareList(list, ast.Typ) - r.resolveList(list) -} - -func (r *resolver) walkBody(body *ast.BlockStmt) { - if body == nil { - return - } - r.openLabelScope() - defer r.closeLabelScope() - r.walkStmts(body.List) -} diff --git a/internal/backport/go/parser/resolver_test.go b/internal/backport/go/parser/resolver_test.go deleted file mode 100644 index 8dd9172845..0000000000 --- a/internal/backport/go/parser/resolver_test.go +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright 2021 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 parser - -import ( - "fmt" - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/internal/typeparams" - "golang.org/x/website/internal/backport/go/scanner" - "golang.org/x/website/internal/backport/go/token" - "os" - "path/filepath" - "strings" - "testing" -) - -// TestResolution checks that identifiers are resolved to the declarations -// annotated in the source, by comparing the positions of the resulting -// Ident.Obj.Decl to positions marked in the source via special comments. -// -// In the test source, any comment prefixed with '=' or '@' (or both) marks the -// previous token position as the declaration ('=') or a use ('@') of an -// identifier. The text following '=' and '@' in the comment string is the -// label to use for the location. Declaration labels must be unique within the -// file, and use labels must refer to an existing declaration label. It's OK -// for a comment to denote both the declaration and use of a label (e.g. -// '=@foo'). Leading and trailing whitespace is ignored. Any comment not -// beginning with '=' or '@' is ignored. -func TestResolution(t *testing.T) { - dir := filepath.Join("testdata", "resolution") - fis, err := os.ReadDir(dir) - if err != nil { - t.Fatal(err) - } - - for _, fi := range fis { - t.Run(fi.Name(), func(t *testing.T) { - fset := token.NewFileSet() - path := filepath.Join(dir, fi.Name()) - src := readFile(path) // panics on failure - var mode Mode - if !strings.HasSuffix(path, ".go2") { - mode |= typeparams.DisallowParsing - } - file, err := ParseFile(fset, path, src, mode) - if err != nil { - t.Fatal(err) - } - - // Compare the positions of objects resolved during parsing (fromParser) - // to those annotated in source comments (fromComments). - - handle := fset.File(file.Package) - fromParser := declsFromParser(file) - fromComments := declsFromComments(handle, src) - - pos := func(pos token.Pos) token.Position { - p := handle.Position(pos) - // The file name is implied by the subtest, so remove it to avoid - // clutter in error messages. - p.Filename = "" - return p - } - for k, want := range fromComments { - if got := fromParser[k]; got != want { - t.Errorf("%s resolved to %s, want %s", pos(k), pos(got), pos(want)) - } - delete(fromParser, k) - } - // What remains in fromParser are unexpected resolutions. - for k, got := range fromParser { - t.Errorf("%s resolved to %s, want no object", pos(k), pos(got)) - } - }) - } -} - -// declsFromParser walks the file and collects the map associating an -// identifier position with its declaration position. -func declsFromParser(file *ast.File) map[token.Pos]token.Pos { - objmap := map[token.Pos]token.Pos{} - ast.Inspect(file, func(node ast.Node) bool { - // Ignore blank identifiers to reduce noise. - if ident, _ := node.(*ast.Ident); ident != nil && ident.Obj != nil && ident.Name != "_" { - objmap[ident.Pos()] = ident.Obj.Pos() - } - return true - }) - return objmap -} - -// declsFromComments looks at comments annotating uses and declarations, and -// maps each identifier use to its corresponding declaration. See the -// description of these annotations in the documentation for TestResolution. -func declsFromComments(handle *token.File, src []byte) map[token.Pos]token.Pos { - decls, uses := positionMarkers(handle, src) - - objmap := make(map[token.Pos]token.Pos) - // Join decls and uses on name, to build the map of use->decl. - for name, posns := range uses { - declpos, ok := decls[name] - if !ok { - panic(fmt.Sprintf("missing declaration for %s", name)) - } - for _, pos := range posns { - objmap[pos] = declpos - } - } - return objmap -} - -// positionMarkers extracts named positions from the source denoted by comments -// prefixed with '=' (declarations) and '@' (uses): for example '@foo' or -// '=@bar'. It returns a map of name->position for declarations, and -// name->position(s) for uses. -func positionMarkers(handle *token.File, src []byte) (decls map[string]token.Pos, uses map[string][]token.Pos) { - var s scanner.Scanner - s.Init(handle, src, nil, scanner.ScanComments) - decls = make(map[string]token.Pos) - uses = make(map[string][]token.Pos) - var prev token.Pos // position of last non-comment, non-semicolon token - -scanFile: - for { - pos, tok, lit := s.Scan() - switch tok { - case token.EOF: - break scanFile - case token.COMMENT: - name, decl, use := annotatedObj(lit) - if len(name) > 0 { - if decl { - if _, ok := decls[name]; ok { - panic(fmt.Sprintf("duplicate declaration markers for %s", name)) - } - decls[name] = prev - } - if use { - uses[name] = append(uses[name], prev) - } - } - case token.SEMICOLON: - // ignore automatically inserted semicolon - if lit == "\n" { - continue scanFile - } - fallthrough - default: - prev = pos - } - } - return decls, uses -} - -func annotatedObj(lit string) (name string, decl, use bool) { - if lit[1] == '*' { - lit = lit[:len(lit)-2] // strip trailing */ - } - lit = strings.TrimSpace(lit[2:]) - -scanLit: - for idx, r := range lit { - switch r { - case '=': - decl = true - case '@': - use = true - default: - name = lit[idx:] - break scanLit - } - } - return -} diff --git a/internal/backport/go/parser/short_test.go b/internal/backport/go/parser/short_test.go deleted file mode 100644 index ada2caa26e..0000000000 --- a/internal/backport/go/parser/short_test.go +++ /dev/null @@ -1,284 +0,0 @@ -// Copyright 2009 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. - -// This file contains test cases for short valid and invalid programs. - -package parser - -import ( - "golang.org/x/website/internal/backport/go/internal/typeparams" - "testing" -) - -var valids = []string{ - "package p\n", - `package p;`, - `package p; import "fmt"; func f() { fmt.Println("Hello, World!") };`, - `package p; func f() { if f(T{}) {} };`, - `package p; func f() { _ = <-chan int(nil) };`, - `package p; func f() { _ = (<-chan int)(nil) };`, - `package p; func f() { _ = (<-chan <-chan int)(nil) };`, - `package p; func f() { _ = <-chan <-chan <-chan <-chan <-int(nil) };`, - `package p; func f(func() func() func());`, - `package p; func f(...T);`, - `package p; func f(float, ...int);`, - `package p; func f(x int, a ...int) { f(0, a...); f(1, a...,) };`, - `package p; func f(int,) {};`, - `package p; func f(...int,) {};`, - `package p; func f(x ...int,) {};`, - `package p; type T []int; var a []bool; func f() { if a[T{42}[0]] {} };`, - `package p; type T []int; func g(int) bool { return true }; func f() { if g(T{42}[0]) {} };`, - `package p; type T []int; func f() { for _ = range []int{T{42}[0]} {} };`, - `package p; var a = T{{1, 2}, {3, 4}}`, - `package p; func f() { select { case <- c: case c <- d: case c <- <- d: case <-c <- d: } };`, - `package p; func f() { select { case x := (<-c): } };`, - `package p; func f() { if ; true {} };`, - `package p; func f() { switch ; {} };`, - `package p; func f() { for _ = range "foo" + "bar" {} };`, - `package p; func f() { var s []int; g(s[:], s[i:], s[:j], s[i:j], s[i:j:k], s[:j:k]) };`, - `package p; var ( _ = (struct {*T}).m; _ = (interface {T}).m )`, - `package p; func ((T),) m() {}`, - `package p; func ((*T),) m() {}`, - `package p; func (*(T),) m() {}`, - `package p; func _(x []int) { for range x {} }`, - `package p; func _() { if [T{}.n]int{} {} }`, - `package p; func _() { map[int]int{}[0]++; map[int]int{}[0] += 1 }`, - `package p; func _(x interface{f()}) { interface{f()}(x).f() }`, - `package p; func _(x chan int) { chan int(x) <- 0 }`, - `package p; const (x = 0; y; z)`, // issue 9639 - `package p; var _ = map[P]int{P{}:0, {}:1}`, - `package p; var _ = map[*P]int{&P{}:0, {}:1}`, - `package p; type T = int`, - `package p; type (T = p.T; _ = struct{}; x = *T)`, - `package p; type T (*int)`, - `package p; type _ struct{ ((int)) }`, - `package p; type _ struct{ (*(int)) }`, - `package p; type _ struct{ ([]byte) }`, // disallowed by type-checker - `package p; var _ = func()T(nil)`, - `package p; func _(T (P))`, - `package p; func _(T []E)`, - `package p; func _(T [P]E)`, - `package p; type _ [A+B]struct{}`, - `package p; func (R) _()`, - `package p; type _ struct{ f [n]E }`, - `package p; type _ struct{ f [a+b+c+d]E }`, - `package p; type I1 interface{}; type I2 interface{ I1 }`, -} - -// validWithTParamsOnly holds source code examples that are valid if -// parseTypeParams is set, but invalid if not. When checking with the -// parseTypeParams set, errors are ignored. -var validWithTParamsOnly = []string{ - `package p; type _ []T[ /* ERROR "expected ';', found '\['" */ int]`, - `package p; type T[P any /* ERROR "expected ']', found any" */ ] struct { P }`, - `package p; type T[P comparable /* ERROR "expected ']', found comparable" */ ] struct { P }`, - `package p; type T[P comparable /* ERROR "expected ']', found comparable" */ [P]] struct { P }`, - `package p; type T[P1, /* ERROR "unexpected comma" */ P2 any] struct { P1; f []P2 }`, - `package p; func _[ /* ERROR "expected '\(', found '\['" */ T any]()()`, - `package p; func _(T (P))`, - `package p; func f[ /* ERROR "expected '\(', found '\['" */ A, B any](); func _() { _ = f[int, int] }`, - `package p; func _(x /* ERROR "mixed named and unnamed parameters" */ T[P1, P2, P3])`, - `package p; func _(x /* ERROR "mixed named and unnamed parameters" */ p.T[Q])`, - `package p; func _(p.T[ /* ERROR "missing ',' in parameter list" */ Q])`, - `package p; type _[A interface /* ERROR "expected ']', found 'interface'" */ {},] struct{}`, - `package p; type _[A interface /* ERROR "expected ']', found 'interface'" */ {}] struct{}`, - `package p; type _[A, /* ERROR "unexpected comma" */ B any,] struct{}`, - `package p; type _[A, /* ERROR "unexpected comma" */ B any] struct{}`, - `package p; type _[A any /* ERROR "expected ']', found any" */,] struct{}`, - `package p; type _[A any /* ERROR "expected ']', found any" */ ]struct{}`, - `package p; type _[A any /* ERROR "expected ']', found any" */ ] struct{ A }`, - `package p; func _[ /* ERROR "expected '\(', found '\['" */ T any]()`, - `package p; func _[ /* ERROR "expected '\(', found '\['" */ T any](x T)`, - `package p; func _[ /* ERROR "expected '\(', found '\['" */ T1, T2 any](x T)`, - `package p; func _[ /* ERROR "expected '\(', found '\['" */ A, B any](a A) B`, - `package p; func _[ /* ERROR "expected '\(', found '\['" */ A, B C](a A) B`, - `package p; func _[ /* ERROR "expected '\(', found '\['" */ A, B C[A, B]](a A) B`, - - `package p; type _[A, /* ERROR "unexpected comma" */ B any] interface { _(a A) B }`, - `package p; type _[A, /* ERROR "unexpected comma" */ B C[A, B]] interface { _(a A) B }`, - `package p; func _[ /* ERROR "expected '\(', found '\['" */ T1, T2 interface{}](x T1) T2`, - `package p; func _[ /* ERROR "expected '\(', found '\['" */ T1 interface{ m() }, T2, T3 interface{}](x T1, y T3) T2`, - `package p; var _ = [ /* ERROR "expected expression" */ ]T[int]{}`, - `package p; var _ = [ /* ERROR "expected expression" */ 10]T[int]{}`, - `package p; var _ = func /* ERROR "expected expression" */ ()T[int]{}`, - `package p; var _ = map /* ERROR "expected expression" */ [T[int]]T[int]{}`, - `package p; var _ = chan /* ERROR "expected expression" */ T[int](x)`, - `package p; func _(_ T[ /* ERROR "missing ',' in parameter list" */ P], T P) T[P]`, - `package p; var _ T[ /* ERROR "expected ';', found '\['" */ chan int]`, - - // TODO(rfindley) this error message could be improved. - `package p; func (_ /* ERROR "mixed named and unnamed parameters" */ R[P]) _(x T)`, - `package p; func (_ /* ERROR "mixed named and unnamed parameters" */ R[ P, Q]) _(x T)`, - - `package p; func (R[P] /* ERROR "missing element type" */ ) _()`, - `package p; func _(T[P] /* ERROR "missing element type" */ )`, - `package p; func _(T[P1, /* ERROR "expected ']', found ','" */ P2, P3 ])`, - `package p; func _(T[P] /* ERROR "missing element type" */ ) T[P]`, - `package p; type _ struct{ T[P] /* ERROR "missing element type" */ }`, - `package p; type _ struct{ T[struct /* ERROR "expected expression" */ {a, b, c int}] }`, - `package p; type _ interface{int| /* ERROR "expected ';'" */ float32; bool; m(); string;}`, - `package p; type I1[T any /* ERROR "expected ']', found any" */ ] interface{}; type I2 interface{ I1[int] }`, - `package p; type I1[T any /* ERROR "expected ']', found any" */ ] interface{}; type I2[T any] interface{ I1[T] }`, - `package p; type _ interface { N[ /* ERROR "expected ';', found '\['" */ T] }`, - `package p; type T[P any /* ERROR "expected ']'" */ ] = T0`, -} - -func TestValid(t *testing.T) { - t.Run("no tparams", func(t *testing.T) { - for _, src := range valids { - checkErrors(t, src, src, DeclarationErrors|AllErrors, false) - } - }) - t.Run("tparams", func(t *testing.T) { - for _, src := range valids { - checkErrors(t, src, src, DeclarationErrors|AllErrors, false) - } - for _, src := range validWithTParamsOnly { - checkErrors(t, src, src, DeclarationErrors|AllErrors, false) - } - }) -} - -// TestSingle is useful to track down a problem with a single short test program. -func TestSingle(t *testing.T) { - const src = `package p; var _ = T{}` - checkErrors(t, src, src, DeclarationErrors|AllErrors, true) -} - -var invalids = []string{ - `foo /* ERROR "expected 'package'" */ !`, - `package p; func f() { if { /* ERROR "missing condition" */ } };`, - `package p; func f() { if ; /* ERROR "missing condition" */ {} };`, - `package p; func f() { if f(); /* ERROR "missing condition" */ {} };`, - `package p; func f() { if _ = range /* ERROR "expected operand" */ x; true {} };`, - `package p; func f() { switch _ /* ERROR "expected switch expression" */ = range x; true {} };`, - `package p; func f() { for _ = range x ; /* ERROR "expected '{'" */ ; {} };`, - `package p; func f() { for ; ; _ = range /* ERROR "expected operand" */ x {} };`, - `package p; func f() { for ; _ /* ERROR "expected boolean or range expression" */ = range x ; {} };`, - `package p; func f() { switch t = /* ERROR "expected ':=', found '='" */ t.(type) {} };`, - `package p; func f() { switch t /* ERROR "expected switch expression" */ , t = t.(type) {} };`, - `package p; func f() { switch t /* ERROR "expected switch expression" */ = t.(type), t {} };`, - `package p; var a = [ /* ERROR "expected expression" */ 1]int;`, - `package p; var a = [ /* ERROR "expected expression" */ ...]int;`, - `package p; var a = struct /* ERROR "expected expression" */ {}`, - `package p; var a = func /* ERROR "expected expression" */ ();`, - `package p; var a = interface /* ERROR "expected expression" */ {}`, - `package p; var a = [ /* ERROR "expected expression" */ ]int`, - `package p; var a = map /* ERROR "expected expression" */ [int]int`, - `package p; var a = chan /* ERROR "expected expression" */ int;`, - `package p; var a = []int{[ /* ERROR "expected expression" */ ]int};`, - `package p; var a = ( /* ERROR "expected expression" */ []int);`, - `package p; var a = <- /* ERROR "expected expression" */ chan int;`, - `package p; func f() { select { case _ <- chan /* ERROR "expected expression" */ int: } };`, - `package p; func f() { _ = (<-<- /* ERROR "expected 'chan'" */ chan int)(nil) };`, - `package p; func f() { _ = (<-chan<-chan<-chan<-chan<-chan<- /* ERROR "expected channel type" */ int)(nil) };`, - `package p; func f() { var t []int; t /* ERROR "expected identifier on left side of :=" */ [0] := 0 };`, - `package p; func f() { if x := g(); x /* ERROR "expected boolean expression" */ = 0 {}};`, - `package p; func f() { _ = x = /* ERROR "expected '=='" */ 0 {}};`, - `package p; func f() { _ = 1 == func()int { var x bool; x = x = /* ERROR "expected '=='" */ true; return x }() };`, - `package p; func f() { var s []int; _ = s[] /* ERROR "expected operand" */ };`, - `package p; func f() { var s []int; _ = s[i:j: /* ERROR "3rd index required" */ ] };`, - `package p; func f() { var s []int; _ = s[i: /* ERROR "2nd index required" */ :k] };`, - `package p; func f() { var s []int; _ = s[i: /* ERROR "2nd index required" */ :] };`, - `package p; func f() { var s []int; _ = s[: /* ERROR "2nd index required" */ :] };`, - `package p; func f() { var s []int; _ = s[: /* ERROR "2nd index required" */ ::] };`, - `package p; func f() { var s []int; _ = s[i:j:k: /* ERROR "expected ']'" */ l] };`, - `package p; func f() { for x /* ERROR "boolean or range expression" */ = []string {} }`, - `package p; func f() { for x /* ERROR "boolean or range expression" */ := []string {} }`, - `package p; func f() { for i /* ERROR "boolean or range expression" */ , x = []string {} }`, - `package p; func f() { for i /* ERROR "boolean or range expression" */ , x := []string {} }`, - `package p; func f() { go f /* ERROR HERE "function must be invoked" */ }`, - `package p; func f() { defer func() {} /* ERROR HERE "function must be invoked" */ }`, - `package p; func f() { go func() { func() { f(x func /* ERROR "missing ','" */ (){}) } } }`, - `package p; func _() (type /* ERROR "found 'type'" */ T)(T)`, - `package p; func (type /* ERROR "found 'type'" */ T)(T) _()`, - `package p; type _[A+B, /* ERROR "unexpected comma" */ ] int`, - - // TODO(rfindley): this error should be positioned on the ':' - `package p; var a = a[[]int:[ /* ERROR "expected expression" */ ]int];`, - - // TODO(rfindley): the compiler error is better here: "cannot parenthesize embedded type" - // TODO(rfindley): confirm that parenthesized types should now be accepted. - // `package p; type I1 interface{}; type I2 interface{ (/* ERROR "expected '}', found '\('" */ I1) }`, - - // issue 8656 - `package p; func f() (a b string /* ERROR "missing ','" */ , ok bool)`, - - // issue 9639 - `package p; var x /* ERROR "missing variable type or initialization" */ , y, z;`, - `package p; const x /* ERROR "missing constant value" */ ;`, - `package p; const x /* ERROR "missing constant value" */ int;`, - `package p; const (x = 0; y; z /* ERROR "missing constant value" */ int);`, - - // issue 12437 - `package p; var _ = struct { x int, /* ERROR "expected ';', found ','" */ }{};`, - `package p; var _ = struct { x int, /* ERROR "expected ';', found ','" */ y float }{};`, - - // issue 11611 - `package p; type _ struct { int, } /* ERROR "expected 'IDENT', found '}'" */ ;`, - `package p; type _ struct { int, float } /* ERROR "expected type, found '}'" */ ;`, - - // issue 13475 - `package p; func f() { if true {} else ; /* ERROR "expected if statement or block" */ }`, - `package p; func f() { if true {} else defer /* ERROR "expected if statement or block" */ f() }`, -} - -// invalidNoTParamErrs holds invalid source code examples annotated with the -// error messages produced when ParseTypeParams is not set. -var invalidNoTParamErrs = []string{ - `package p; type _[_ any /* ERROR "expected ']', found any" */ ] int; var _ = T[]{}`, - `package p; type T[P any /* ERROR "expected ']', found any" */ ] = T0`, - `package p; var _ func[ /* ERROR "expected '\(', found '\['" */ T any](T)`, - `package p; func _[ /* ERROR "expected '\(', found '\['" */ ]()`, - `package p; type _[A, /* ERROR "unexpected comma" */] struct{ A }`, - `package p; func _[ /* ERROR "expected '\(', found '\['" */ type P, *Q interface{}]()`, - - `package p; func (T) _[ /* ERROR "expected '\(', found '\['" */ A, B any](a A) B`, - `package p; func (T) _[ /* ERROR "expected '\(', found '\['" */ A, B C](a A) B`, - `package p; func (T) _[ /* ERROR "expected '\(', found '\['" */ A, B C[A, B]](a A) B`, - - `package p; func(*T[ /* ERROR "missing ',' in parameter list" */ e, e]) _()`, -} - -// invalidTParamErrs holds invalid source code examples annotated with the -// error messages produced when ParseTypeParams is set. -var invalidTParamErrs = []string{ - `package p; type _[_ any] int; var _ = T[] /* ERROR "expected operand" */ {}`, - `package p; var _ func[ /* ERROR "must have no type parameters" */ T any](T)`, - `package p; func _[]/* ERROR "empty type parameter list" */()`, - - // TODO(rfindley) a better location would be after the ']' - `package p; type _[A /* ERROR "all type parameters must be named" */ ,] struct{ A }`, - - // TODO(rfindley) this error is confusing. - `package p; func _[type /* ERROR "all type parameters must be named" */ P, *Q interface{}]()`, - - `package p; func (T) _[ /* ERROR "must have no type parameters" */ A, B any](a A) B`, - `package p; func (T) _[ /* ERROR "must have no type parameters" */ A, B C](a A) B`, - `package p; func (T) _[ /* ERROR "must have no type parameters" */ A, B C[A, B]](a A) B`, - - `package p; func(*T[e, e /* ERROR "e redeclared" */ ]) _()`, -} - -func TestInvalid(t *testing.T) { - t.Run("no tparams", func(t *testing.T) { - for _, src := range invalids { - checkErrors(t, src, src, DeclarationErrors|AllErrors|typeparams.DisallowParsing, true) - } - for _, src := range validWithTParamsOnly { - checkErrors(t, src, src, DeclarationErrors|AllErrors|typeparams.DisallowParsing, true) - } - for _, src := range invalidNoTParamErrs { - checkErrors(t, src, src, DeclarationErrors|AllErrors|typeparams.DisallowParsing, true) - } - }) - t.Run("tparams", func(t *testing.T) { - for _, src := range invalids { - checkErrors(t, src, src, DeclarationErrors|AllErrors, true) - } - for _, src := range invalidTParamErrs { - checkErrors(t, src, src, DeclarationErrors|AllErrors, true) - } - }) -} diff --git a/internal/backport/go/parser/testdata/chans.go2 b/internal/backport/go/parser/testdata/chans.go2 deleted file mode 100644 index fad2bcec9d..0000000000 --- a/internal/backport/go/parser/testdata/chans.go2 +++ /dev/null @@ -1,62 +0,0 @@ -package chans - -import "runtime" - -// Ranger returns a Sender and a Receiver. The Receiver provides a -// Next method to retrieve values. The Sender provides a Send method -// to send values and a Close method to stop sending values. The Next -// method indicates when the Sender has been closed, and the Send -// method indicates when the Receiver has been freed. -// -// This is a convenient way to exit a goroutine sending values when -// the receiver stops reading them. -func Ranger[T any]() (*Sender[T], *Receiver[T]) { - c := make(chan T) - d := make(chan bool) - s := &Sender[T]{values: c, done: d} - r := &Receiver[T]{values: c, done: d} - runtime.SetFinalizer(r, r.finalize) - return s, r -} - -// A sender is used to send values to a Receiver. -type Sender[T any] struct { - values chan<- T - done <-chan bool -} - -// Send sends a value to the receiver. It returns whether any more -// values may be sent; if it returns false the value was not sent. -func (s *Sender[T]) Send(v T) bool { - select { - case s.values <- v: - return true - case <-s.done: - return false - } -} - -// Close tells the receiver that no more values will arrive. -// After Close is called, the Sender may no longer be used. -func (s *Sender[T]) Close() { - close(s.values) -} - -// A Receiver receives values from a Sender. -type Receiver[T any] struct { - values <-chan T - done chan<- bool -} - -// Next returns the next value from the channel. The bool result -// indicates whether the value is valid, or whether the Sender has -// been closed and no more values will be received. -func (r *Receiver[T]) Next() (T, bool) { - v, ok := <-r.values - return v, ok -} - -// finalize is a finalizer for the receiver. -func (r *Receiver[T]) finalize() { - close(r.done) -} diff --git a/internal/backport/go/parser/testdata/commas.src b/internal/backport/go/parser/testdata/commas.src deleted file mode 100644 index e0603cf9f7..0000000000 --- a/internal/backport/go/parser/testdata/commas.src +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2012 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. - -// Test case for error messages/parser synchronization -// after missing commas. - -package p - -var _ = []int{ - 0/* ERROR HERE "missing ','" */ -} - -var _ = []int{ - 0, - 1, - 2, - 3/* ERROR HERE "missing ','" */ -} diff --git a/internal/backport/go/parser/testdata/interface.go2 b/internal/backport/go/parser/testdata/interface.go2 deleted file mode 100644 index 2ed9339c52..0000000000 --- a/internal/backport/go/parser/testdata/interface.go2 +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2021 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. - -// This file contains test cases for interfaces containing -// constraint elements. - -package p - -type _ interface { - m() - ~int - ~int|string - E -} - -type _ interface { - m() - ~int - int | string - int | ~string - ~int | ~string -} - -type _ interface { - m() - ~int - T[int, string] | string - int | ~T[string, struct{}] - ~int | ~string -} - -type _ interface { - int - []byte - [10]int - struct{} - *int - func() - interface{} - map[string]int - chan T - chan<- T - <-chan T - T[int] -} - -type _ interface { - int | string - []byte | string - [10]int | string - struct{} | string - *int | string - func() | string - interface{} | string - map[string]int | string - chan T | string - chan<- T | string - <-chan T | string - T[int] | string -} - -type _ interface { - ~int | string - ~[]byte | string - ~[10]int | string - ~struct{} | string - ~*int | string - ~func() | string - ~interface{} | string - ~map[string]int | string - ~chan T | string - ~chan<- T | string - ~<-chan T | string - ~T[int] | string -} diff --git a/internal/backport/go/parser/testdata/issue11377.src b/internal/backport/go/parser/testdata/issue11377.src deleted file mode 100644 index 1c438003eb..0000000000 --- a/internal/backport/go/parser/testdata/issue11377.src +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2018 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. - -// Test case for issue 11377: Better synchronization of -// parser after certain syntax errors. - -package p - -func bad1() { - if f()) /* ERROR "expected ';', found '\)'" */ { - return - } -} - -// There shouldn't be any errors down below. - -func F1() {} -func F2() {} -func F3() {} -func F4() {} -func F5() {} -func F6() {} -func F7() {} -func F8() {} -func F9() {} -func F10() {} diff --git a/internal/backport/go/parser/testdata/issue23434.src b/internal/backport/go/parser/testdata/issue23434.src deleted file mode 100644 index 24a0832347..0000000000 --- a/internal/backport/go/parser/testdata/issue23434.src +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2018 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. - -// Test case for issue 23434: Better synchronization of -// parser after missing type. There should be exactly -// one error each time, with now follow errors. - -package p - -func g() { - m := make(map[string]! /* ERROR "expected type, found '!'" */ ) - for { - x := 1 - print(x) - } -} - -func f() { - m := make(map[string]) /* ERROR "expected type, found '\)'" */ - for { - x := 1 - print(x) - } -} diff --git a/internal/backport/go/parser/testdata/issue3106.src b/internal/backport/go/parser/testdata/issue3106.src deleted file mode 100644 index 2db10be235..0000000000 --- a/internal/backport/go/parser/testdata/issue3106.src +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2012 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. - -// Test case for issue 3106: Better synchronization of -// parser after certain syntax errors. - -package main - -func f() { - var m Mutex - c := MakeCond(&m) - percent := 0 - const step = 10 - for i := 0; i < 5; i++ { - go func() { - for { - // Emulates some useful work. - time.Sleep(1e8) - m.Lock() - defer - if /* ERROR "expected ';', found 'if'" */ percent == 100 { - m.Unlock() - break - } - percent++ - if percent % step == 0 { - //c.Signal() - } - m.Unlock() - } - }() - } - for { - m.Lock() - if percent == 0 || percent % step != 0 { - c.Wait() - } - fmt.Print(",") - if percent == 100 { - m.Unlock() - break - } - m.Unlock() - } -} diff --git a/internal/backport/go/parser/testdata/issue34946.src b/internal/backport/go/parser/testdata/issue34946.src deleted file mode 100644 index 6bb15e10c7..0000000000 --- a/internal/backport/go/parser/testdata/issue34946.src +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2019 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. - -// Test case for issue 34946: Better synchronization of -// parser for function declarations that start their -// body's opening { on a new line. - -package p - -// accept Allman/BSD-style declaration but complain -// (implicit semicolon between signature and body) -func _() int -{ /* ERROR "unexpected semicolon or newline before {" */ - { return 0 } -} - -func _() {} - -func _(); { /* ERROR "unexpected semicolon or newline before {" */ } - -func _() {} diff --git a/internal/backport/go/parser/testdata/issue42951/not_a_file.go/invalid.go b/internal/backport/go/parser/testdata/issue42951/not_a_file.go/invalid.go deleted file mode 100644 index bb698be11c..0000000000 --- a/internal/backport/go/parser/testdata/issue42951/not_a_file.go/invalid.go +++ /dev/null @@ -1 +0,0 @@ -This file should not be parsed by ParseDir. diff --git a/internal/backport/go/parser/testdata/issue44504.src b/internal/backport/go/parser/testdata/issue44504.src deleted file mode 100644 index 7791f4a809..0000000000 --- a/internal/backport/go/parser/testdata/issue44504.src +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2021 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. - -// Test case for issue 44504: panic due to duplicate resolution of slice/index -// operands. We should not try to resolve a LHS expression with invalid syntax. - -package p - -func _() { - var items []bool - items[] /* ERROR "operand" */ = false -} diff --git a/internal/backport/go/parser/testdata/issue49174.go2 b/internal/backport/go/parser/testdata/issue49174.go2 deleted file mode 100644 index 77c195083f..0000000000 --- a/internal/backport/go/parser/testdata/issue49174.go2 +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2021 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 p - -func _[_ []int | int]() {} -func _[_ int | []int]() {} diff --git a/internal/backport/go/parser/testdata/issue49175.go2 b/internal/backport/go/parser/testdata/issue49175.go2 deleted file mode 100644 index a5ad30f14c..0000000000 --- a/internal/backport/go/parser/testdata/issue49175.go2 +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2021 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 p - -type _[_ []t]t -type _[_ [1]t]t - -func _[_ []t]() {} -func _[_ [1]t]() {} - -type t [t /* ERROR "all type parameters must be named" */ [0]]t diff --git a/internal/backport/go/parser/testdata/issue49482.go2 b/internal/backport/go/parser/testdata/issue49482.go2 deleted file mode 100644 index d8385bed4f..0000000000 --- a/internal/backport/go/parser/testdata/issue49482.go2 +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2021 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 p - -type ( - // these need a comma to disambiguate - _[P *T,] struct{} - _[P *T, _ any] struct{} - _[P (*T),] struct{} - _[P (*T), _ any] struct{} - _[P (T),] struct{} - _[P (T), _ any] struct{} - - // these parse as name followed by type - _[P *struct{}] struct{} - _[P (*struct{})] struct{} - _[P ([]int)] struct{} - - // array declarations - _ [P(T)]struct{} - _ [P((T))]struct{} - _ [P * *T]struct{} - _ [P * T]struct{} - _ [P(*T)]struct{} - _ [P(**T)]struct{} - _ [P * T - T]struct{} - _ [P*T-T, /* ERROR "unexpected comma" */ ]struct{} - _ [10, /* ERROR "unexpected comma" */ ]struct{} - - _[P *struct{}|int] struct{} - _[P *struct{}|int|string] struct{} -) diff --git a/internal/backport/go/parser/testdata/issue50427.go2 b/internal/backport/go/parser/testdata/issue50427.go2 deleted file mode 100644 index 15214594e2..0000000000 --- a/internal/backport/go/parser/testdata/issue50427.go2 +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2022 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 p - -type T interface{ m[ /* ERROR "must have no type parameters" */ P any]() } - -func _(t T) { - var _ interface{ m[ /* ERROR "must have no type parameters" */ P any](); n() } = t -} - -type S struct{} - -func (S) m[ /* ERROR "must have no type parameters" */ P any]() {} - -func _(s S) { - var _ interface{ m[ /* ERROR "must have no type parameters" */ P any](); n() } = s -} diff --git a/internal/backport/go/parser/testdata/linalg.go2 b/internal/backport/go/parser/testdata/linalg.go2 deleted file mode 100644 index 7ccb19c08d..0000000000 --- a/internal/backport/go/parser/testdata/linalg.go2 +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2019 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 linalg - -import "math" - -// Numeric is type bound that matches any numeric type. -// It would likely be in a constraints package in the standard library. -type Numeric interface { - ~int|~int8|~int16|~int32|~int64| - ~uint|~uint8|~uint16|~uint32|~uint64|~uintptr| - ~float32|~float64| - ~complex64|~complex128 -} - -func DotProduct[T Numeric](s1, s2 []T) T { - if len(s1) != len(s2) { - panic("DotProduct: slices of unequal length") - } - var r T - for i := range s1 { - r += s1[i] * s2[i] - } - return r -} - -// NumericAbs matches numeric types with an Abs method. -type NumericAbs[T any] interface { - Numeric - - Abs() T -} - -// AbsDifference computes the absolute value of the difference of -// a and b, where the absolute value is determined by the Abs method. -func AbsDifference[T NumericAbs](a, b T) T { - d := a - b - return d.Abs() -} - -// OrderedNumeric is a type bound that matches numeric types that support the < operator. -type OrderedNumeric interface { - ~int|~int8|~int16|~int32|~int64| - ~uint|~uint8|~uint16|~uint32|~uint64|~uintptr| - ~float32|~float64 -} - -// Complex is a type bound that matches the two complex types, which do not have a < operator. -type Complex interface { - ~complex64|~complex128 -} - -// OrderedAbs is a helper type that defines an Abs method for -// ordered numeric types. -type OrderedAbs[T OrderedNumeric] T - -func (a OrderedAbs[T]) Abs() OrderedAbs[T] { - if a < 0 { - return -a - } - return a -} - -// ComplexAbs is a helper type that defines an Abs method for -// complex types. -type ComplexAbs[T Complex] T - -func (a ComplexAbs[T]) Abs() ComplexAbs[T] { - r := float64(real(a)) - i := float64(imag(a)) - d := math.Sqrt(r * r + i * i) - return ComplexAbs[T](complex(d, 0)) -} - -func OrderedAbsDifference[T OrderedNumeric](a, b T) T { - return T(AbsDifference(OrderedAbs[T](a), OrderedAbs[T](b))) -} - -func ComplexAbsDifference[T Complex](a, b T) T { - return T(AbsDifference(ComplexAbs[T](a), ComplexAbs[T](b))) -} diff --git a/internal/backport/go/parser/testdata/map.go2 b/internal/backport/go/parser/testdata/map.go2 deleted file mode 100644 index 74c79ae44f..0000000000 --- a/internal/backport/go/parser/testdata/map.go2 +++ /dev/null @@ -1,109 +0,0 @@ -// Package orderedmap provides an ordered map, implemented as a binary tree. -package orderedmap - -import "chans" - -// Map is an ordered map. -type Map[K, V any] struct { - root *node[K, V] - compare func(K, K) int -} - -// node is the type of a node in the binary tree. -type node[K, V any] struct { - key K - val V - left, right *node[K, V] -} - -// New returns a new map. -func New[K, V any](compare func(K, K) int) *Map[K, V] { - return &Map[K, V]{compare: compare} -} - -// find looks up key in the map, and returns either a pointer -// to the node holding key, or a pointer to the location where -// such a node would go. -func (m *Map[K, V]) find(key K) **node[K, V] { - pn := &m.root - for *pn != nil { - switch cmp := m.compare(key, (*pn).key); { - case cmp < 0: - pn = &(*pn).left - case cmp > 0: - pn = &(*pn).right - default: - return pn - } - } - return pn -} - -// Insert inserts a new key/value into the map. -// If the key is already present, the value is replaced. -// Returns true if this is a new key, false if already present. -func (m *Map[K, V]) Insert(key K, val V) bool { - pn := m.find(key) - if *pn != nil { - (*pn).val = val - return false - } - *pn = &node[K, V]{key: key, val: val} - return true -} - -// Find returns the value associated with a key, or zero if not present. -// The found result reports whether the key was found. -func (m *Map[K, V]) Find(key K) (V, bool) { - pn := m.find(key) - if *pn == nil { - var zero V // see the discussion of zero values, above - return zero, false - } - return (*pn).val, true -} - -// keyValue is a pair of key and value used when iterating. -type keyValue[K, V any] struct { - key K - val V -} - -// InOrder returns an iterator that does an in-order traversal of the map. -func (m *Map[K, V]) InOrder() *Iterator[K, V] { - sender, receiver := chans.Ranger[keyValue[K, V]]() - var f func(*node[K, V]) bool - f = func(n *node[K, V]) bool { - if n == nil { - return true - } - // Stop sending values if sender.Send returns false, - // meaning that nothing is listening at the receiver end. - return f(n.left) && - // TODO - // sender.Send(keyValue[K, V]{n.key, n.val}) && - f(n.right) - } - go func() { - f(m.root) - sender.Close() - }() - return &Iterator{receiver} -} - -// Iterator is used to iterate over the map. -type Iterator[K, V any] struct { - r *chans.Receiver[keyValue[K, V]] -} - -// Next returns the next key and value pair, and a boolean indicating -// whether they are valid or whether we have reached the end. -func (it *Iterator[K, V]) Next() (K, V, bool) { - keyval, ok := it.r.Next() - if !ok { - var zerok K - var zerov V - return zerok, zerov, false - } - return keyval.key, keyval.val, true -} diff --git a/internal/backport/go/parser/testdata/metrics.go2 b/internal/backport/go/parser/testdata/metrics.go2 deleted file mode 100644 index ef1c66b241..0000000000 --- a/internal/backport/go/parser/testdata/metrics.go2 +++ /dev/null @@ -1,58 +0,0 @@ -package metrics - -import "sync" - -type Metric1[T comparable] struct { - mu sync.Mutex - m map[T]int -} - -func (m *Metric1[T]) Add(v T) { - m.mu.Lock() - defer m.mu.Unlock() - if m.m == nil { - m.m = make(map[T]int) - } - m[v]++ -} - -type key2[T1, T2 comparable] struct { - f1 T1 - f2 T2 -} - -type Metric2[T1, T2 cmp2] struct { - mu sync.Mutex - m map[key2[T1, T2]]int -} - -func (m *Metric2[T1, T2]) Add(v1 T1, v2 T2) { - m.mu.Lock() - defer m.mu.Unlock() - if m.m == nil { - m.m = make(map[key2[T1, T2]]int) - } - m[key[T1, T2]{v1, v2}]++ -} - -type key3[T1, T2, T3 comparable] struct { - f1 T1 - f2 T2 - f3 T3 -} - -type Metric3[T1, T2, T3 comparable] struct { - mu sync.Mutex - m map[key3[T1, T2, T3]]int -} - -func (m *Metric3[T1, T2, T3]) Add(v1 T1, v2 T2, v3 T3) { - m.mu.Lock() - defer m.mu.Unlock() - if m.m == nil { - m.m = make(map[key3]int) - } - m[key[T1, T2, T3]{v1, v2, v3}]++ -} - -// Repeat for the maximum number of permitted arguments. diff --git a/internal/backport/go/parser/testdata/resolution/issue45136.src b/internal/backport/go/parser/testdata/resolution/issue45136.src deleted file mode 100644 index e1d63d8377..0000000000 --- a/internal/backport/go/parser/testdata/resolution/issue45136.src +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2021 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 issue45136 - -type obj /* =@obj */ struct { - name /*=@name */ string -} - -func _() { - var foo /* =@foo */ = "foo" - obj /* @obj */ ["foo"] - obj /* @obj */ .run() - obj /* @obj */ { - name: foo /* @foo */, - } - obj /* @obj */ { - name: "bar", - }.run() - - var _ = File{key: obj /* @obj */ {}} - var _ = File{obj /* @obj */ {}} - - []obj /* @obj */ {foo /* @foo */} - x /* =@x1 */ := obj /* @obj */{} -} diff --git a/internal/backport/go/parser/testdata/resolution/issue45160.src b/internal/backport/go/parser/testdata/resolution/issue45160.src deleted file mode 100644 index 6be933b783..0000000000 --- a/internal/backport/go/parser/testdata/resolution/issue45160.src +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2021 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 issue45160 - -func mklink1 /* =@mklink1func */() {} - -func _() { - var tests /* =@tests */ = []dirLinkTest /* @dirLinkTest */ { - { - mklink1 /* @mklink1func */: func() {}, - mklink2: func(link /* =@link */, target /* =@target */ string) error { - return nil - }, - }, - } -} - -type dirLinkTest /* =@dirLinkTest */ struct { - mklink1 /* =@mklink1field */ func(string, string) error - mklink2 /* =@mklink2field */ func(string, string) error -} - -func mklink2 /* =@mklink2func */() {} diff --git a/internal/backport/go/parser/testdata/resolution/resolution.src b/internal/backport/go/parser/testdata/resolution/resolution.src deleted file mode 100644 index a880dd1c5e..0000000000 --- a/internal/backport/go/parser/testdata/resolution/resolution.src +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2021 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 resolution - -func f /* =@fdecl */(n /* =@narg */ ast.Node) bool { - if n /* =@ninit */, ok /* =@ok */ := n /* @narg */ .(*ast.SelectorExpr); ok /* @ok */ { - sel = n /* @ninit */ - } -} - -type c /* =@cdecl */ map[token.Pos]resolvedObj - -func (v /* =@vdecl */ c /* @cdecl */) Visit(node /* =@nodearg */ ast.Node) (w /* =@w */ ast.Visitor) {} - -const ( - basic /* =@basic */ = iota - labelOk // =@labelOk -) - -type T /* =@T */ int - -func _(count /* =@count */ T /* @T */) { - x /* =@x1 */ := c /* @cdecl */{} - switch x /* =@x2 */ := x /* @x1 */; x /* =@x3 */ := x /* @x2 */.(type) { - case c /* @cdecl */: - default: - } -loop /* =@loop */: - for { - if true { - break loop /* @loop */ - } - } - select { - case err /* =@err1 */ := <-_: - return err /* @err1 */ - case err /* =@err2 */ := <-_: - return err /* @err2 */ - } - - _ = func(p1 /* =@p1 */ int, p2 /* =@p2 */ p1) { - closed /* =@closed */ := p1 // @p1 - shadowed /* =@shadowed1 */ := p2 // @p2 - _ = func(shadowed /* =@shadowed2 */ p2 /* @p2 */) { - closed /* @closed */ = 1 - shadowed /* @shadowed2 */ = 2 - } - } -} - -func (r /* =@r */ c /* @cdecl */) m(_ r) c /* @cdecl */ { return r /* @r */ } - -var cycle /* =@cycle */ = cycle /* @cycle */ + 1 - -type chain /* =@chain */ struct { - next /* =@next */ *chain /* @chain */ -} - -func recursive /* =@recursive */() { - recursive /* @recursive */ () -} diff --git a/internal/backport/go/parser/testdata/resolution/typeparams.go2 b/internal/backport/go/parser/testdata/resolution/typeparams.go2 deleted file mode 100644 index 7395ca2a34..0000000000 --- a/internal/backport/go/parser/testdata/resolution/typeparams.go2 +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2021 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 resolution - -type List /* =@List */ [E /* =@E */ any] []E // @E - -type Pair /* =@Pair */ [L /* =@L */, R /* =@R */ any] struct { - Left /* =@Left */ L // @L - Right /* =@Right */ R // @R - L /* =@Lfield */ int -} - -var _ = Pair /* @Pair */ [int, string]{} - -type Addable /* =@Addable */ interface { - ~int64|~float64 -} - -func Add /* =@AddDecl */[T /* =@T */ Addable /* @Addable */](l /* =@l */, r /* =@r */ T /* @T */) T /* @T */ { - var t /* =@t */ T /* @T */ - return l /* @l */ + r /* @r */ + t /* @t */ -} - -type Receiver /* =@Receiver */[P /* =@P */ any] struct {} - -type RP /* =@RP1 */ struct{} - -// TODO(rFindley): make a decision on how/whether to resolve identifiers that -// refer to receiver type parameters, as is the case for the 'P' result -// parameter below. -// -// For now, we ensure that types are not incorrectly resolved when receiver -// type parameters are in scope. -func (r /* =@recv */ Receiver /* @Receiver */ [RP]) m(RP) RP {} - -func f /* =@f */[T1 /* =@T1 */ interface{~[]T2 /* @T2 */}, T2 /* =@T2 */ any]( - x /* =@x */ T1 /* @T1 */, T1 /* =@T1_duplicate */ y, // Note that this is a bug: - // the duplicate T1 should - // not be allowed. - ){ - // Note that duplicate short var declarations resolve to their alt declaration. - x /* @x */ := 0 - y /* =@y */ := 0 - T1 /* @T1 */ := 0 - var t1var /* =@t1var */ T1 /* @T1 */ -} - -// From issue #39634 -func(*ph1[e, e])h(d) diff --git a/internal/backport/go/parser/testdata/set.go2 b/internal/backport/go/parser/testdata/set.go2 deleted file mode 100644 index 0da6377cbd..0000000000 --- a/internal/backport/go/parser/testdata/set.go2 +++ /dev/null @@ -1,31 +0,0 @@ -// Package set implements sets of any type. -package set - -type Set[Elem comparable] map[Elem]struct{} - -func Make[Elem comparable]() Set[Elem] { - return make(Set(Elem)) -} - -func (s Set[Elem]) Add(v Elem) { - s[v] = struct{}{} -} - -func (s Set[Elem]) Delete(v Elem) { - delete(s, v) -} - -func (s Set[Elem]) Contains(v Elem) bool { - _, ok := s[v] - return ok -} - -func (s Set[Elem]) Len() int { - return len(s) -} - -func (s Set[Elem]) Iterate(f func(Elem)) { - for v := range s { - f(v) - } -} diff --git a/internal/backport/go/parser/testdata/slices.go2 b/internal/backport/go/parser/testdata/slices.go2 deleted file mode 100644 index e060212f29..0000000000 --- a/internal/backport/go/parser/testdata/slices.go2 +++ /dev/null @@ -1,31 +0,0 @@ -// Package slices implements various slice algorithms. -package slices - -// Map turns a []T1 to a []T2 using a mapping function. -func Map[T1, T2 any](s []T1, f func(T1) T2) []T2 { - r := make([]T2, len(s)) - for i, v := range s { - r[i] = f(v) - } - return r -} - -// Reduce reduces a []T1 to a single value using a reduction function. -func Reduce[T1, T2 any](s []T1, initializer T2, f func(T2, T1) T2) T2 { - r := initializer - for _, v := range s { - r = f(r, v) - } - return r -} - -// Filter filters values from a slice using a filter function. -func Filter[T any](s []T, f func(T) bool) []T { - var r []T - for _, v := range s { - if f(v) { - r = append(r, v) - } - } - return r -} diff --git a/internal/backport/go/parser/testdata/sort.go2 b/internal/backport/go/parser/testdata/sort.go2 deleted file mode 100644 index 88be79f966..0000000000 --- a/internal/backport/go/parser/testdata/sort.go2 +++ /dev/null @@ -1,27 +0,0 @@ -package sort - -type orderedSlice[Elem comparable] []Elem - -func (s orderedSlice[Elem]) Len() int { return len(s) } -func (s orderedSlice[Elem]) Less(i, j int) bool { return s[i] < s[j] } -func (s orderedSlice[Elem]) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - -// OrderedSlice sorts the slice s in ascending order. -// The elements of s must be ordered using the < operator. -func OrderedSlice[Elem comparable](s []Elem) { - sort.Sort(orderedSlice[Elem](s)) -} - -type sliceFn[Elem any] struct { - s []Elem - f func(Elem, Elem) bool -} - -func (s sliceFn[Elem]) Len() int { return len(s.s) } -func (s sliceFn[Elem]) Less(i, j int) bool { return s.f(s.s[i], s.s[j]) } -func (s sliceFn[Elem]) Swap(i, j int) { s.s[i], s.s[j] = s.s[j], s.s[i] } - -// SliceFn sorts the slice s according to the function f. -func SliceFn[Elem any](s []Elem, f func(Elem, Elem) bool) { - Sort(sliceFn[Elem]{s, f}) -} diff --git a/internal/backport/go/parser/testdata/tparams.go2 b/internal/backport/go/parser/testdata/tparams.go2 deleted file mode 100644 index 28fd132243..0000000000 --- a/internal/backport/go/parser/testdata/tparams.go2 +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2020 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 p - -type _[a /* ERROR "all type parameters must be named" */, b] struct{} -type _[a t, b t, c /* ERROR "all type parameters must be named" */ ] struct{} -type _ struct { - t [n]byte - t[a] - t[a, b] -} -type _ interface { - t[a] - m[ /* ERROR "method must have no type parameters" */ _ _, /* ERROR mixed */ _]() - t[a, b] -} - -func _[] /* ERROR "empty type parameter list" */ () -func _[a /* ERROR "all type parameters must be named" */, b ]() -func _[a t, b t, c /* ERROR "all type parameters must be named" */ ]() - -// TODO(rfindley) incorrect error message (see existing TODO in parser) -func f[a b, 0 /* ERROR "expected '\)', found 0" */ ] () - -// issue #49482 -type ( - _[a *[]int] struct{} - _[a *t,] struct{} - _[a *t|[]int] struct{} - _[a *t|t,] struct{} - _[a *t|~t,] struct{} - _[a *struct{}|t] struct{} - _[a *t|struct{}] struct{} - _[a *struct{}|~t] struct{} -) - -// issue #51488 -type ( - _[a *t|t,] struct{} - _[a *t|t, b t] struct{} - _[a *t|t] struct{} - _[a *[]t|t] struct{} - _[a ([]t)] struct{} - _[a ([]t)|t] struct{} -) diff --git a/internal/backport/go/parser/testdata/typeparams.src b/internal/backport/go/parser/testdata/typeparams.src deleted file mode 100644 index 479cb96871..0000000000 --- a/internal/backport/go/parser/testdata/typeparams.src +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2021 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. - -// Test cases for error messages produced while parsing code that uses type -// parameters, without ParseTypeParams being enabled. - -package p - -type List[E any /* ERROR "expected ']', found any" */ ] []E - -type Pair[L, /* ERROR "unexpected comma" */ R any] struct { - Left L - Right R -} - -var _ = Pair[int, /* ERROR "expected ']' or ':', found ','" */ string]{} diff --git a/internal/backport/go/parser/testdata/typeset.go2 b/internal/backport/go/parser/testdata/typeset.go2 deleted file mode 100644 index 7844c22212..0000000000 --- a/internal/backport/go/parser/testdata/typeset.go2 +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2021 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. - -// This file contains test cases for typeset-only constraint elements. -// TODO(gri) gofmt once/if gofmt supports this notation. - -package p - -type ( - _[_ t] t - _[_ ~t] t - _[_ t|t] t - _[_ ~t|t] t - _[_ t|~t] t - _[_ ~t|~t] t - - _[_ t, _, _ t|t] t - _[_ t, _, _ ~t|t] t - _[_ t, _, _ t|~t] t - _[_ t, _, _ ~t|~t] t - - _[_ t.t] t - _[_ ~t.t] t - _[_ t.t|t.t] t - _[_ ~t.t|t.t] t - _[_ t.t|~t.t] t - _[_ ~t.t|~t.t] t - - _[_ t, _, _ t.t|t.t] t - _[_ t, _, _ ~t.t|t.t] t - _[_ t, _, _ t.t|~t.t] t - _[_ t, _, _ ~t.t|~t.t] t - - _[_ struct{}] t - _[_ ~struct{}] t - - _[_ struct{}|t] t - _[_ ~struct{}|t] t - _[_ struct{}|~t] t - _[_ ~struct{}|~t] t - - _[_ t|struct{}] t - _[_ ~t|struct{}] t - _[_ t|~struct{}] t - _[_ ~t|~struct{}] t -) - -// Single-expression type parameter lists and those that don't start -// with a (type parameter) name are considered array sizes. -// The term must be a valid expression (it could be a type incl. a -// tilde term) but the type-checker will complain. -type ( - _[t] t - _[t|t] t - - // These are invalid and the type-checker will complain. - _[~t] t - _[~t|t] t - _[t|~t] t - _[~t|~t] t -) - -type _[_ t, t /* ERROR "type parameters must be named" */ ] t -type _[_ ~t, t /* ERROR "type parameters must be named" */ ] t -type _[_ t, ~ /* ERROR "type parameters must be named" */ t] t -type _[_ ~t, ~ /* ERROR "type parameters must be named" */ t] t - -type _[_ t|t, t /* ERROR "type parameters must be named" */ |t] t -type _[_ ~t|t, t /* ERROR "type parameters must be named" */ |t] t -type _[_ t|t, ~ /* ERROR "type parameters must be named" */ t|t] t -type _[_ ~t|t, ~ /* ERROR "type parameters must be named" */ t|t] t diff --git a/internal/backport/go/printer/comment.go b/internal/backport/go/printer/comment.go deleted file mode 100644 index 663c528d07..0000000000 --- a/internal/backport/go/printer/comment.go +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright 2022 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 printer - -import ( - "strings" - - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/doc/comment" -) - -// formatDocComment reformats the doc comment list, -// returning the canonical formatting. -func formatDocComment(list []*ast.Comment) []*ast.Comment { - // Extract comment text (removing comment markers). - var kind, text string - var directives []*ast.Comment - if len(list) == 1 && strings.HasPrefix(list[0].Text, "/*") { - kind = "/*" - text = list[0].Text - if !strings.Contains(text, "\n") || allStars(text) { - // Single-line /* .. */ comment in doc comment position, - // or multiline old-style comment like - // /* - // * Comment - // * text here. - // */ - // Should not happen, since it will not work well as a - // doc comment, but if it does, just ignore: - // reformatting it will only make the situation worse. - return list - } - text = text[2 : len(text)-2] // cut /* and */ - } else if strings.HasPrefix(list[0].Text, "//") { - kind = "//" - var b strings.Builder - for _, c := range list { - if !strings.HasPrefix(c.Text, "//") { - return list - } - // Accumulate //go:build etc lines separately. - if isDirective(c.Text[2:]) { - directives = append(directives, c) - continue - } - b.WriteString(strings.TrimPrefix(c.Text[2:], " ")) - b.WriteString("\n") - } - text = b.String() - } else { - // Not sure what this is, so leave alone. - return list - } - - if text == "" { - return list - } - - // Parse comment and reformat as text. - var p comment.Parser - d := p.Parse(text) - - var pr comment.Printer - text = string(pr.Comment(d)) - - // For /* */ comment, return one big comment with text inside. - slash := list[0].Slash - if kind == "/*" { - c := &ast.Comment{ - Slash: slash, - Text: "/*\n" + text + "*/", - } - return []*ast.Comment{c} - } - - // For // comment, return sequence of // lines. - var out []*ast.Comment - for text != "" { - var line string - line, text, _ = stringsCut(text, "\n") - if line == "" { - line = "//" - } else if strings.HasPrefix(line, "\t") { - line = "//" + line - } else { - line = "// " + line - } - out = append(out, &ast.Comment{ - Slash: slash, - Text: line, - }) - } - if len(directives) > 0 { - out = append(out, &ast.Comment{ - Slash: slash, - Text: "//", - }) - for _, c := range directives { - out = append(out, &ast.Comment{ - Slash: slash, - Text: c.Text, - }) - } - } - return out -} - -// isDirective reports whether c is a comment directive. -// See go.dev/issue/37974. -// This code is also in go/ast. -func isDirective(c string) bool { - // "//line " is a line directive. - // "//extern " is for gccgo. - // "//export " is for cgo. - // (The // has been removed.) - if strings.HasPrefix(c, "line ") || strings.HasPrefix(c, "extern ") || strings.HasPrefix(c, "export ") { - return true - } - - // "//[a-z0-9]+:[a-z0-9]" - // (The // has been removed.) - colon := strings.Index(c, ":") - if colon <= 0 || colon+1 >= len(c) { - return false - } - for i := 0; i <= colon+1; i++ { - if i == colon { - continue - } - b := c[i] - if !('a' <= b && b <= 'z' || '0' <= b && b <= '9') { - return false - } - } - return true -} - -// allStars reports whether text is the interior of an -// old-style /* */ comment with a star at the start of each line. -func allStars(text string) bool { - for i := 0; i < len(text); i++ { - if text[i] == '\n' { - j := i + 1 - for j < len(text) && (text[j] == ' ' || text[j] == '\t') { - j++ - } - if j < len(text) && text[j] != '*' { - return false - } - } - } - return true -} diff --git a/internal/backport/go/printer/example_test.go b/internal/backport/go/printer/example_test.go deleted file mode 100644 index 0df644d918..0000000000 --- a/internal/backport/go/printer/example_test.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2012 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 printer_test - -import ( - "bytes" - "fmt" - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/parser" - "golang.org/x/website/internal/backport/go/printer" - "golang.org/x/website/internal/backport/go/token" - "strings" - "testing" -) - -// Dummy test function so that godoc does not use the entire file as example. -func Test(*testing.T) {} - -func parseFunc(filename, functionname string) (fun *ast.FuncDecl, fset *token.FileSet) { - fset = token.NewFileSet() - if file, err := parser.ParseFile(fset, filename, nil, 0); err == nil { - for _, d := range file.Decls { - if f, ok := d.(*ast.FuncDecl); ok && f.Name.Name == functionname { - fun = f - return - } - } - } - panic("function not found") -} - -func ExampleFprint() { - // Parse source file and extract the AST without comments for - // this function, with position information referring to the - // file set fset. - funcAST, fset := parseFunc("example_test.go", "ExampleFprint") - - // Print the function body into buffer buf. - // The file set is provided to the printer so that it knows - // about the original source formatting and can add additional - // line breaks where they were present in the source. - var buf bytes.Buffer - printer.Fprint(&buf, fset, funcAST.Body) - - // Remove braces {} enclosing the function body, unindent, - // and trim leading and trailing white space. - s := buf.String() - s = s[1 : len(s)-1] - s = strings.TrimSpace(strings.ReplaceAll(s, "\n\t", "\n")) - - // Print the cleaned-up body text to stdout. - fmt.Println(s) - - // output: - // funcAST, fset := parseFunc("example_test.go", "ExampleFprint") - // - // var buf bytes.Buffer - // printer.Fprint(&buf, fset, funcAST.Body) - // - // s := buf.String() - // s = s[1 : len(s)-1] - // s = strings.TrimSpace(strings.ReplaceAll(s, "\n\t", "\n")) - // - // fmt.Println(s) -} diff --git a/internal/backport/go/printer/gobuild.go b/internal/backport/go/printer/gobuild.go deleted file mode 100644 index f00492d077..0000000000 --- a/internal/backport/go/printer/gobuild.go +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright 2020 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 printer - -import ( - "go/build/constraint" - "sort" - "text/tabwriter" -) - -func (p *printer) fixGoBuildLines() { - if len(p.goBuild)+len(p.plusBuild) == 0 { - return - } - - // Find latest possible placement of //go:build and // +build comments. - // That's just after the last blank line before we find a non-comment. - // (We'll add another blank line after our comment block.) - // When we start dropping // +build comments, we can skip over /* */ comments too. - // Note that we are processing tabwriter input, so every comment - // begins and ends with a tabwriter.Escape byte. - // And some newlines have turned into \f bytes. - insert := 0 - for pos := 0; ; { - // Skip leading space at beginning of line. - blank := true - for pos < len(p.output) && (p.output[pos] == ' ' || p.output[pos] == '\t') { - pos++ - } - // Skip over // comment if any. - if pos+3 < len(p.output) && p.output[pos] == tabwriter.Escape && p.output[pos+1] == '/' && p.output[pos+2] == '/' { - blank = false - for pos < len(p.output) && !isNL(p.output[pos]) { - pos++ - } - } - // Skip over \n at end of line. - if pos >= len(p.output) || !isNL(p.output[pos]) { - break - } - pos++ - - if blank { - insert = pos - } - } - - // If there is a //go:build comment before the place we identified, - // use that point instead. (Earlier in the file is always fine.) - if len(p.goBuild) > 0 && p.goBuild[0] < insert { - insert = p.goBuild[0] - } else if len(p.plusBuild) > 0 && p.plusBuild[0] < insert { - insert = p.plusBuild[0] - } - - var x constraint.Expr - switch len(p.goBuild) { - case 0: - // Synthesize //go:build expression from // +build lines. - for _, pos := range p.plusBuild { - y, err := constraint.Parse(p.commentTextAt(pos)) - if err != nil { - x = nil - break - } - if x == nil { - x = y - } else { - x = &constraint.AndExpr{X: x, Y: y} - } - } - case 1: - // Parse //go:build expression. - x, _ = constraint.Parse(p.commentTextAt(p.goBuild[0])) - } - - var block []byte - if x == nil { - // Don't have a valid //go:build expression to treat as truth. - // Bring all the lines together but leave them alone. - // Note that these are already tabwriter-escaped. - for _, pos := range p.goBuild { - block = append(block, p.lineAt(pos)...) - } - for _, pos := range p.plusBuild { - block = append(block, p.lineAt(pos)...) - } - } else { - block = append(block, tabwriter.Escape) - block = append(block, "//go:build "...) - block = append(block, x.String()...) - block = append(block, tabwriter.Escape, '\n') - if len(p.plusBuild) > 0 { - lines, err := constraint.PlusBuildLines(x) - if err != nil { - lines = []string{"// +build error: " + err.Error()} - } - for _, line := range lines { - block = append(block, tabwriter.Escape) - block = append(block, line...) - block = append(block, tabwriter.Escape, '\n') - } - } - } - block = append(block, '\n') - - // Build sorted list of lines to delete from remainder of output. - toDelete := append(p.goBuild, p.plusBuild...) - sort.Ints(toDelete) - - // Collect output after insertion point, with lines deleted, into after. - var after []byte - start := insert - for _, end := range toDelete { - if end < start { - continue - } - after = appendLines(after, p.output[start:end]) - start = end + len(p.lineAt(end)) - } - after = appendLines(after, p.output[start:]) - if n := len(after); n >= 2 && isNL(after[n-1]) && isNL(after[n-2]) { - after = after[:n-1] - } - - p.output = p.output[:insert] - p.output = append(p.output, block...) - p.output = append(p.output, after...) -} - -// appendLines is like append(x, y...) -// but it avoids creating doubled blank lines, -// which would not be gofmt-standard output. -// It assumes that only whole blocks of lines are being appended, -// not line fragments. -func appendLines(x, y []byte) []byte { - if len(y) > 0 && isNL(y[0]) && // y starts in blank line - (len(x) == 0 || len(x) >= 2 && isNL(x[len(x)-1]) && isNL(x[len(x)-2])) { // x is empty or ends in blank line - y = y[1:] // delete y's leading blank line - } - return append(x, y...) -} - -func (p *printer) lineAt(start int) []byte { - pos := start - for pos < len(p.output) && !isNL(p.output[pos]) { - pos++ - } - if pos < len(p.output) { - pos++ - } - return p.output[start:pos] -} - -func (p *printer) commentTextAt(start int) string { - if start < len(p.output) && p.output[start] == tabwriter.Escape { - start++ - } - pos := start - for pos < len(p.output) && p.output[pos] != tabwriter.Escape && !isNL(p.output[pos]) { - pos++ - } - return string(p.output[start:pos]) -} - -func isNL(b byte) bool { - return b == '\n' || b == '\f' -} diff --git a/internal/backport/go/printer/nodes.go b/internal/backport/go/printer/nodes.go deleted file mode 100644 index 8358c91a92..0000000000 --- a/internal/backport/go/printer/nodes.go +++ /dev/null @@ -1,1921 +0,0 @@ -// Copyright 2009 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. - -// This file implements printing of AST nodes; specifically -// expressions, statements, declarations, and files. It uses -// the print functionality implemented in printer.go. - -package printer - -import ( - "bytes" - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/token" - "math" - "strconv" - "strings" - "unicode" - "unicode/utf8" -) - -// Formatting issues: -// - better comment formatting for /*-style comments at the end of a line (e.g. a declaration) -// when the comment spans multiple lines; if such a comment is just two lines, formatting is -// not idempotent -// - formatting of expression lists -// - should use blank instead of tab to separate one-line function bodies from -// the function header unless there is a group of consecutive one-liners - -// ---------------------------------------------------------------------------- -// Common AST nodes. - -// Print as many newlines as necessary (but at least min newlines) to get to -// the current line. ws is printed before the first line break. If newSection -// is set, the first line break is printed as formfeed. Returns 0 if no line -// breaks were printed, returns 1 if there was exactly one newline printed, -// and returns a value > 1 if there was a formfeed or more than one newline -// printed. -// -// TODO(gri): linebreak may add too many lines if the next statement at "line" -// is preceded by comments because the computation of n assumes -// the current position before the comment and the target position -// after the comment. Thus, after interspersing such comments, the -// space taken up by them is not considered to reduce the number of -// linebreaks. At the moment there is no easy way to know about -// future (not yet interspersed) comments in this function. -func (p *printer) linebreak(line, min int, ws whiteSpace, newSection bool) (nbreaks int) { - n := nlimit(line - p.pos.Line) - if n < min { - n = min - } - if n > 0 { - p.print(ws) - if newSection { - p.print(formfeed) - n-- - nbreaks = 2 - } - nbreaks += n - for ; n > 0; n-- { - p.print(newline) - } - } - return -} - -// setComment sets g as the next comment if g != nil and if node comments -// are enabled - this mode is used when printing source code fragments such -// as exports only. It assumes that there is no pending comment in p.comments -// and at most one pending comment in the p.comment cache. -func (p *printer) setComment(g *ast.CommentGroup) { - if g == nil || !p.useNodeComments { - return - } - if p.comments == nil { - // initialize p.comments lazily - p.comments = make([]*ast.CommentGroup, 1) - } else if p.cindex < len(p.comments) { - // for some reason there are pending comments; this - // should never happen - handle gracefully and flush - // all comments up to g, ignore anything after that - p.flush(p.posFor(g.List[0].Pos()), token.ILLEGAL) - p.comments = p.comments[0:1] - // in debug mode, report error - p.internalError("setComment found pending comments") - } - p.comments[0] = g - p.cindex = 0 - // don't overwrite any pending comment in the p.comment cache - // (there may be a pending comment when a line comment is - // immediately followed by a lead comment with no other - // tokens between) - if p.commentOffset == infinity { - p.nextComment() // get comment ready for use - } -} - -type exprListMode uint - -const ( - commaTerm exprListMode = 1 << iota // list is optionally terminated by a comma - noIndent // no extra indentation in multi-line lists -) - -// If indent is set, a multi-line identifier list is indented after the -// first linebreak encountered. -func (p *printer) identList(list []*ast.Ident, indent bool) { - // convert into an expression list so we can re-use exprList formatting - xlist := make([]ast.Expr, len(list)) - for i, x := range list { - xlist[i] = x - } - var mode exprListMode - if !indent { - mode = noIndent - } - p.exprList(token.NoPos, xlist, 1, mode, token.NoPos, false) -} - -const filteredMsg = "contains filtered or unexported fields" - -// Print a list of expressions. If the list spans multiple -// source lines, the original line breaks are respected between -// expressions. -// -// TODO(gri) Consider rewriting this to be independent of []ast.Expr -// so that we can use the algorithm for any kind of list -// -// (e.g., pass list via a channel over which to range). -func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exprListMode, next0 token.Pos, isIncomplete bool) { - if len(list) == 0 { - if isIncomplete { - prev := p.posFor(prev0) - next := p.posFor(next0) - if prev.IsValid() && prev.Line == next.Line { - p.print("/* " + filteredMsg + " */") - } else { - p.print(newline) - p.print(indent, "// "+filteredMsg, unindent, newline) - } - } - return - } - - prev := p.posFor(prev0) - next := p.posFor(next0) - line := p.lineFor(list[0].Pos()) - endLine := p.lineFor(list[len(list)-1].End()) - - if prev.IsValid() && prev.Line == line && line == endLine { - // all list entries on a single line - for i, x := range list { - if i > 0 { - // use position of expression following the comma as - // comma position for correct comment placement - p.print(x.Pos(), token.COMMA, blank) - } - p.expr0(x, depth) - } - if isIncomplete { - p.print(token.COMMA, blank, "/* "+filteredMsg+" */") - } - return - } - - // list entries span multiple lines; - // use source code positions to guide line breaks - - // Don't add extra indentation if noIndent is set; - // i.e., pretend that the first line is already indented. - ws := ignore - if mode&noIndent == 0 { - ws = indent - } - - // The first linebreak is always a formfeed since this section must not - // depend on any previous formatting. - prevBreak := -1 // index of last expression that was followed by a linebreak - if prev.IsValid() && prev.Line < line && p.linebreak(line, 0, ws, true) > 0 { - ws = ignore - prevBreak = 0 - } - - // initialize expression/key size: a zero value indicates expr/key doesn't fit on a single line - size := 0 - - // We use the ratio between the geometric mean of the previous key sizes and - // the current size to determine if there should be a break in the alignment. - // To compute the geometric mean we accumulate the ln(size) values (lnsum) - // and the number of sizes included (count). - lnsum := 0.0 - count := 0 - - // print all list elements - prevLine := prev.Line - for i, x := range list { - line = p.lineFor(x.Pos()) - - // Determine if the next linebreak, if any, needs to use formfeed: - // in general, use the entire node size to make the decision; for - // key:value expressions, use the key size. - // TODO(gri) for a better result, should probably incorporate both - // the key and the node size into the decision process - useFF := true - - // Determine element size: All bets are off if we don't have - // position information for the previous and next token (likely - // generated code - simply ignore the size in this case by setting - // it to 0). - prevSize := size - const infinity = 1e6 // larger than any source line - size = p.nodeSize(x, infinity) - pair, isPair := x.(*ast.KeyValueExpr) - if size <= infinity && prev.IsValid() && next.IsValid() { - // x fits on a single line - if isPair { - size = p.nodeSize(pair.Key, infinity) // size <= infinity - } - } else { - // size too large or we don't have good layout information - size = 0 - } - - // If the previous line and the current line had single- - // line-expressions and the key sizes are small or the - // ratio between the current key and the geometric mean - // if the previous key sizes does not exceed a threshold, - // align columns and do not use formfeed. - if prevSize > 0 && size > 0 { - const smallSize = 40 - if count == 0 || prevSize <= smallSize && size <= smallSize { - useFF = false - } else { - const r = 2.5 // threshold - geomean := math.Exp(lnsum / float64(count)) // count > 0 - ratio := float64(size) / geomean - useFF = r*ratio <= 1 || r <= ratio - } - } - - needsLinebreak := 0 < prevLine && prevLine < line - if i > 0 { - // Use position of expression following the comma as - // comma position for correct comment placement, but - // only if the expression is on the same line. - if !needsLinebreak { - p.print(x.Pos()) - } - p.print(token.COMMA) - needsBlank := true - if needsLinebreak { - // Lines are broken using newlines so comments remain aligned - // unless useFF is set or there are multiple expressions on - // the same line in which case formfeed is used. - nbreaks := p.linebreak(line, 0, ws, useFF || prevBreak+1 < i) - if nbreaks > 0 { - ws = ignore - prevBreak = i - needsBlank = false // we got a line break instead - } - // If there was a new section or more than one new line - // (which means that the tabwriter will implicitly break - // the section), reset the geomean variables since we are - // starting a new group of elements with the next element. - if nbreaks > 1 { - lnsum = 0 - count = 0 - } - } - if needsBlank { - p.print(blank) - } - } - - if len(list) > 1 && isPair && size > 0 && needsLinebreak { - // We have a key:value expression that fits onto one line - // and it's not on the same line as the prior expression: - // Use a column for the key such that consecutive entries - // can align if possible. - // (needsLinebreak is set if we started a new line before) - p.expr(pair.Key) - p.print(pair.Colon, token.COLON, vtab) - p.expr(pair.Value) - } else { - p.expr0(x, depth) - } - - if size > 0 { - lnsum += math.Log(float64(size)) - count++ - } - - prevLine = line - } - - if mode&commaTerm != 0 && next.IsValid() && p.pos.Line < next.Line { - // Print a terminating comma if the next token is on a new line. - p.print(token.COMMA) - if isIncomplete { - p.print(newline) - p.print("// " + filteredMsg) - } - if ws == ignore && mode&noIndent == 0 { - // unindent if we indented - p.print(unindent) - } - p.print(formfeed) // terminating comma needs a line break to look good - return - } - - if isIncomplete { - p.print(token.COMMA, newline) - p.print("// "+filteredMsg, newline) - } - - if ws == ignore && mode&noIndent == 0 { - // unindent if we indented - p.print(unindent) - } -} - -type paramMode int - -const ( - funcParam paramMode = iota - funcTParam - typeTParam -) - -func (p *printer) parameters(fields *ast.FieldList, mode paramMode) { - openTok, closeTok := token.LPAREN, token.RPAREN - if mode != funcParam { - openTok, closeTok = token.LBRACK, token.RBRACK - } - p.print(fields.Opening, openTok) - if len(fields.List) > 0 { - prevLine := p.lineFor(fields.Opening) - ws := indent - for i, par := range fields.List { - // determine par begin and end line (may be different - // if there are multiple parameter names for this par - // or the type is on a separate line) - parLineBeg := p.lineFor(par.Pos()) - parLineEnd := p.lineFor(par.End()) - // separating "," if needed - needsLinebreak := 0 < prevLine && prevLine < parLineBeg - if i > 0 { - // use position of parameter following the comma as - // comma position for correct comma placement, but - // only if the next parameter is on the same line - if !needsLinebreak { - p.print(par.Pos()) - } - p.print(token.COMMA) - } - // separator if needed (linebreak or blank) - if needsLinebreak && p.linebreak(parLineBeg, 0, ws, true) > 0 { - // break line if the opening "(" or previous parameter ended on a different line - ws = ignore - } else if i > 0 { - p.print(blank) - } - // parameter names - if len(par.Names) > 0 { - // Very subtle: If we indented before (ws == ignore), identList - // won't indent again. If we didn't (ws == indent), identList will - // indent if the identList spans multiple lines, and it will outdent - // again at the end (and still ws == indent). Thus, a subsequent indent - // by a linebreak call after a type, or in the next multi-line identList - // will do the right thing. - p.identList(par.Names, ws == indent) - p.print(blank) - } - // parameter type - p.expr(stripParensAlways(par.Type)) - prevLine = parLineEnd - } - - // if the closing ")" is on a separate line from the last parameter, - // print an additional "," and line break - if closing := p.lineFor(fields.Closing); 0 < prevLine && prevLine < closing { - p.print(token.COMMA) - p.linebreak(closing, 0, ignore, true) - } else if mode == typeTParam && fields.NumFields() == 1 && combinesWithName(fields.List[0].Type) { - // A type parameter list [P T] where the name P and the type expression T syntactically - // combine to another valid (value) expression requires a trailing comma, as in [P *T,] - // (or an enclosing interface as in [P interface(*T)]), so that the type parameter list - // is not parsed as an array length [P*T]. - p.print(token.COMMA) - } - - // unindent if we indented - if ws == ignore { - p.print(unindent) - } - } - - p.print(fields.Closing, closeTok) -} - -// combinesWithName reports whether a name followed by the expression x -// syntactically combines to another valid (value) expression. For instance -// using *T for x, "name *T" syntactically appears as the expression x*T. -// On the other hand, using P|Q or *P|~Q for x, "name P|Q" or name *P|~Q" -// cannot be combined into a valid (value) expression. -func combinesWithName(x ast.Expr) bool { - switch x := x.(type) { - case *ast.StarExpr: - // name *x.X combines to name*x.X if x.X is not a type element - return !isTypeElem(x.X) - case *ast.BinaryExpr: - return combinesWithName(x.X) && !isTypeElem(x.Y) - case *ast.ParenExpr: - // name(x) combines but we are making sure at - // the call site that x is never parenthesized. - panic("unexpected parenthesized expression") - } - return false -} - -// isTypeElem reports whether x is a (possibly parenthesized) type element expression. -// The result is false if x could be a type element OR an ordinary (value) expression. -func isTypeElem(x ast.Expr) bool { - switch x := x.(type) { - case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType: - return true - case *ast.UnaryExpr: - return x.Op == token.TILDE - case *ast.BinaryExpr: - return isTypeElem(x.X) || isTypeElem(x.Y) - case *ast.ParenExpr: - return isTypeElem(x.X) - } - return false -} - -func (p *printer) signature(sig *ast.FuncType) { - if sig.TypeParams != nil { - p.parameters(sig.TypeParams, funcTParam) - } - if sig.Params != nil { - p.parameters(sig.Params, funcParam) - } else { - p.print(token.LPAREN, token.RPAREN) - } - res := sig.Results - n := res.NumFields() - if n > 0 { - // res != nil - p.print(blank) - if n == 1 && res.List[0].Names == nil { - // single anonymous res; no ()'s - p.expr(stripParensAlways(res.List[0].Type)) - return - } - p.parameters(res, funcParam) - } -} - -func identListSize(list []*ast.Ident, maxSize int) (size int) { - for i, x := range list { - if i > 0 { - size += len(", ") - } - size += utf8.RuneCountInString(x.Name) - if size >= maxSize { - break - } - } - return -} - -func (p *printer) isOneLineFieldList(list []*ast.Field) bool { - if len(list) != 1 { - return false // allow only one field - } - f := list[0] - if f.Tag != nil || f.Comment != nil { - return false // don't allow tags or comments - } - // only name(s) and type - const maxSize = 30 // adjust as appropriate, this is an approximate value - namesSize := identListSize(f.Names, maxSize) - if namesSize > 0 { - namesSize = 1 // blank between names and types - } - typeSize := p.nodeSize(f.Type, maxSize) - return namesSize+typeSize <= maxSize -} - -func (p *printer) setLineComment(text string) { - p.setComment(&ast.CommentGroup{List: []*ast.Comment{{Slash: token.NoPos, Text: text}}}) -} - -func (p *printer) fieldList(fields *ast.FieldList, isStruct, isIncomplete bool) { - lbrace := fields.Opening - list := fields.List - rbrace := fields.Closing - hasComments := isIncomplete || p.commentBefore(p.posFor(rbrace)) - srcIsOneLine := lbrace.IsValid() && rbrace.IsValid() && p.lineFor(lbrace) == p.lineFor(rbrace) - - if !hasComments && srcIsOneLine { - // possibly a one-line struct/interface - if len(list) == 0 { - // no blank between keyword and {} in this case - p.print(lbrace, token.LBRACE, rbrace, token.RBRACE) - return - } else if p.isOneLineFieldList(list) { - // small enough - print on one line - // (don't use identList and ignore source line breaks) - p.print(lbrace, token.LBRACE, blank) - f := list[0] - if isStruct { - for i, x := range f.Names { - if i > 0 { - // no comments so no need for comma position - p.print(token.COMMA, blank) - } - p.expr(x) - } - if len(f.Names) > 0 { - p.print(blank) - } - p.expr(f.Type) - } else { // interface - if len(f.Names) > 0 { - name := f.Names[0] // method name - p.expr(name) - p.signature(f.Type.(*ast.FuncType)) // don't print "func" - } else { - // embedded interface - p.expr(f.Type) - } - } - p.print(blank, rbrace, token.RBRACE) - return - } - } - // hasComments || !srcIsOneLine - - p.print(blank, lbrace, token.LBRACE, indent) - if hasComments || len(list) > 0 { - p.print(formfeed) - } - - if isStruct { - - sep := vtab - if len(list) == 1 { - sep = blank - } - var line int - for i, f := range list { - if i > 0 { - p.linebreak(p.lineFor(f.Pos()), 1, ignore, p.linesFrom(line) > 0) - } - extraTabs := 0 - p.setComment(f.Doc) - p.recordLine(&line) - if len(f.Names) > 0 { - // named fields - p.identList(f.Names, false) - p.print(sep) - p.expr(f.Type) - extraTabs = 1 - } else { - // anonymous field - p.expr(f.Type) - extraTabs = 2 - } - if f.Tag != nil { - if len(f.Names) > 0 && sep == vtab { - p.print(sep) - } - p.print(sep) - p.expr(f.Tag) - extraTabs = 0 - } - if f.Comment != nil { - for ; extraTabs > 0; extraTabs-- { - p.print(sep) - } - p.setComment(f.Comment) - } - } - if isIncomplete { - if len(list) > 0 { - p.print(formfeed) - } - p.flush(p.posFor(rbrace), token.RBRACE) // make sure we don't lose the last line comment - p.setLineComment("// " + filteredMsg) - } - - } else { // interface - - var line int - var prev *ast.Ident // previous "type" identifier - for i, f := range list { - var name *ast.Ident // first name, or nil - if len(f.Names) > 0 { - name = f.Names[0] - } - if i > 0 { - // don't do a line break (min == 0) if we are printing a list of types - // TODO(gri) this doesn't work quite right if the list of types is - // spread across multiple lines - min := 1 - if prev != nil && name == prev { - min = 0 - } - p.linebreak(p.lineFor(f.Pos()), min, ignore, p.linesFrom(line) > 0) - } - p.setComment(f.Doc) - p.recordLine(&line) - if name != nil { - // method - p.expr(name) - p.signature(f.Type.(*ast.FuncType)) // don't print "func" - prev = nil - } else { - // embedded interface - p.expr(f.Type) - prev = nil - } - p.setComment(f.Comment) - } - if isIncomplete { - if len(list) > 0 { - p.print(formfeed) - } - p.flush(p.posFor(rbrace), token.RBRACE) // make sure we don't lose the last line comment - p.setLineComment("// contains filtered or unexported methods") - } - - } - p.print(unindent, formfeed, rbrace, token.RBRACE) -} - -// ---------------------------------------------------------------------------- -// Expressions - -func walkBinary(e *ast.BinaryExpr) (has4, has5 bool, maxProblem int) { - switch e.Op.Precedence() { - case 4: - has4 = true - case 5: - has5 = true - } - - switch l := e.X.(type) { - case *ast.BinaryExpr: - if l.Op.Precedence() < e.Op.Precedence() { - // parens will be inserted. - // pretend this is an *ast.ParenExpr and do nothing. - break - } - h4, h5, mp := walkBinary(l) - has4 = has4 || h4 - has5 = has5 || h5 - if maxProblem < mp { - maxProblem = mp - } - } - - switch r := e.Y.(type) { - case *ast.BinaryExpr: - if r.Op.Precedence() <= e.Op.Precedence() { - // parens will be inserted. - // pretend this is an *ast.ParenExpr and do nothing. - break - } - h4, h5, mp := walkBinary(r) - has4 = has4 || h4 - has5 = has5 || h5 - if maxProblem < mp { - maxProblem = mp - } - - case *ast.StarExpr: - if e.Op == token.QUO { // `*/` - maxProblem = 5 - } - - case *ast.UnaryExpr: - switch e.Op.String() + r.Op.String() { - case "/*", "&&", "&^": - maxProblem = 5 - case "++", "--": - if maxProblem < 4 { - maxProblem = 4 - } - } - } - return -} - -func cutoff(e *ast.BinaryExpr, depth int) int { - has4, has5, maxProblem := walkBinary(e) - if maxProblem > 0 { - return maxProblem + 1 - } - if has4 && has5 { - if depth == 1 { - return 5 - } - return 4 - } - if depth == 1 { - return 6 - } - return 4 -} - -func diffPrec(expr ast.Expr, prec int) int { - x, ok := expr.(*ast.BinaryExpr) - if !ok || prec != x.Op.Precedence() { - return 1 - } - return 0 -} - -func reduceDepth(depth int) int { - depth-- - if depth < 1 { - depth = 1 - } - return depth -} - -// Format the binary expression: decide the cutoff and then format. -// Let's call depth == 1 Normal mode, and depth > 1 Compact mode. -// (Algorithm suggestion by Russ Cox.) -// -// The precedences are: -// -// 5 * / % << >> & &^ -// 4 + - | ^ -// 3 == != < <= > >= -// 2 && -// 1 || -// -// The only decision is whether there will be spaces around levels 4 and 5. -// There are never spaces at level 6 (unary), and always spaces at levels 3 and below. -// -// To choose the cutoff, look at the whole expression but excluding primary -// expressions (function calls, parenthesized exprs), and apply these rules: -// -// 1. If there is a binary operator with a right side unary operand -// that would clash without a space, the cutoff must be (in order): -// -// /* 6 -// && 6 -// &^ 6 -// ++ 5 -// -- 5 -// -// (Comparison operators always have spaces around them.) -// -// 2. If there is a mix of level 5 and level 4 operators, then the cutoff -// is 5 (use spaces to distinguish precedence) in Normal mode -// and 4 (never use spaces) in Compact mode. -// -// 3. If there are no level 4 operators or no level 5 operators, then the -// cutoff is 6 (always use spaces) in Normal mode -// and 4 (never use spaces) in Compact mode. -func (p *printer) binaryExpr(x *ast.BinaryExpr, prec1, cutoff, depth int) { - prec := x.Op.Precedence() - if prec < prec1 { - // parenthesis needed - // Note: The parser inserts an ast.ParenExpr node; thus this case - // can only occur if the AST is created in a different way. - p.print(token.LPAREN) - p.expr0(x, reduceDepth(depth)) // parentheses undo one level of depth - p.print(token.RPAREN) - return - } - - printBlank := prec < cutoff - - ws := indent - p.expr1(x.X, prec, depth+diffPrec(x.X, prec)) - if printBlank { - p.print(blank) - } - xline := p.pos.Line // before the operator (it may be on the next line!) - yline := p.lineFor(x.Y.Pos()) - p.print(x.OpPos, x.Op) - if xline != yline && xline > 0 && yline > 0 { - // at least one line break, but respect an extra empty line - // in the source - if p.linebreak(yline, 1, ws, true) > 0 { - ws = ignore - printBlank = false // no blank after line break - } - } - if printBlank { - p.print(blank) - } - p.expr1(x.Y, prec+1, depth+1) - if ws == ignore { - p.print(unindent) - } -} - -func isBinary(expr ast.Expr) bool { - _, ok := expr.(*ast.BinaryExpr) - return ok -} - -func (p *printer) expr1(expr ast.Expr, prec1, depth int) { - p.print(expr.Pos()) - - switch x := expr.(type) { - case *ast.BadExpr: - p.print("BadExpr") - - case *ast.Ident: - p.print(x) - - case *ast.BinaryExpr: - if depth < 1 { - p.internalError("depth < 1:", depth) - depth = 1 - } - p.binaryExpr(x, prec1, cutoff(x, depth), depth) - - case *ast.KeyValueExpr: - p.expr(x.Key) - p.print(x.Colon, token.COLON, blank) - p.expr(x.Value) - - case *ast.StarExpr: - const prec = token.UnaryPrec - if prec < prec1 { - // parenthesis needed - p.print(token.LPAREN) - p.print(token.MUL) - p.expr(x.X) - p.print(token.RPAREN) - } else { - // no parenthesis needed - p.print(token.MUL) - p.expr(x.X) - } - - case *ast.UnaryExpr: - const prec = token.UnaryPrec - if prec < prec1 { - // parenthesis needed - p.print(token.LPAREN) - p.expr(x) - p.print(token.RPAREN) - } else { - // no parenthesis needed - p.print(x.Op) - if x.Op == token.RANGE { - // TODO(gri) Remove this code if it cannot be reached. - p.print(blank) - } - p.expr1(x.X, prec, depth) - } - - case *ast.BasicLit: - if p.Config.Mode&normalizeNumbers != 0 { - x = normalizedNumber(x) - } - p.print(x) - - case *ast.FuncLit: - p.print(x.Type.Pos(), token.FUNC) - // See the comment in funcDecl about how the header size is computed. - startCol := p.out.Column - len("func") - p.signature(x.Type) - p.funcBody(p.distanceFrom(x.Type.Pos(), startCol), blank, x.Body) - - case *ast.ParenExpr: - if _, hasParens := x.X.(*ast.ParenExpr); hasParens { - // don't print parentheses around an already parenthesized expression - // TODO(gri) consider making this more general and incorporate precedence levels - p.expr0(x.X, depth) - } else { - p.print(token.LPAREN) - p.expr0(x.X, reduceDepth(depth)) // parentheses undo one level of depth - p.print(x.Rparen, token.RPAREN) - } - - case *ast.SelectorExpr: - p.selectorExpr(x, depth, false) - - case *ast.TypeAssertExpr: - p.expr1(x.X, token.HighestPrec, depth) - p.print(token.PERIOD, x.Lparen, token.LPAREN) - if x.Type != nil { - p.expr(x.Type) - } else { - p.print(token.TYPE) - } - p.print(x.Rparen, token.RPAREN) - - case *ast.IndexExpr: - // TODO(gri): should treat[] like parentheses and undo one level of depth - p.expr1(x.X, token.HighestPrec, 1) - p.print(x.Lbrack, token.LBRACK) - p.expr0(x.Index, depth+1) - p.print(x.Rbrack, token.RBRACK) - - case *ast.IndexListExpr: - // TODO(gri): as for IndexExpr, should treat [] like parentheses and undo - // one level of depth - p.expr1(x.X, token.HighestPrec, 1) - p.print(x.Lbrack, token.LBRACK) - p.exprList(x.Lbrack, x.Indices, depth+1, commaTerm, x.Rbrack, false) - p.print(x.Rbrack, token.RBRACK) - - case *ast.SliceExpr: - // TODO(gri): should treat[] like parentheses and undo one level of depth - p.expr1(x.X, token.HighestPrec, 1) - p.print(x.Lbrack, token.LBRACK) - indices := []ast.Expr{x.Low, x.High} - if x.Max != nil { - indices = append(indices, x.Max) - } - // determine if we need extra blanks around ':' - var needsBlanks bool - if depth <= 1 { - var indexCount int - var hasBinaries bool - for _, x := range indices { - if x != nil { - indexCount++ - if isBinary(x) { - hasBinaries = true - } - } - } - if indexCount > 1 && hasBinaries { - needsBlanks = true - } - } - for i, x := range indices { - if i > 0 { - if indices[i-1] != nil && needsBlanks { - p.print(blank) - } - p.print(token.COLON) - if x != nil && needsBlanks { - p.print(blank) - } - } - if x != nil { - p.expr0(x, depth+1) - } - } - p.print(x.Rbrack, token.RBRACK) - - case *ast.CallExpr: - if len(x.Args) > 1 { - depth++ - } - var wasIndented bool - if _, ok := x.Fun.(*ast.FuncType); ok { - // conversions to literal function types require parentheses around the type - p.print(token.LPAREN) - wasIndented = p.possibleSelectorExpr(x.Fun, token.HighestPrec, depth) - p.print(token.RPAREN) - } else { - wasIndented = p.possibleSelectorExpr(x.Fun, token.HighestPrec, depth) - } - p.print(x.Lparen, token.LPAREN) - if x.Ellipsis.IsValid() { - p.exprList(x.Lparen, x.Args, depth, 0, x.Ellipsis, false) - p.print(x.Ellipsis, token.ELLIPSIS) - if x.Rparen.IsValid() && p.lineFor(x.Ellipsis) < p.lineFor(x.Rparen) { - p.print(token.COMMA, formfeed) - } - } else { - p.exprList(x.Lparen, x.Args, depth, commaTerm, x.Rparen, false) - } - p.print(x.Rparen, token.RPAREN) - if wasIndented { - p.print(unindent) - } - - case *ast.CompositeLit: - // composite literal elements that are composite literals themselves may have the type omitted - if x.Type != nil { - p.expr1(x.Type, token.HighestPrec, depth) - } - p.level++ - p.print(x.Lbrace, token.LBRACE) - p.exprList(x.Lbrace, x.Elts, 1, commaTerm, x.Rbrace, x.Incomplete) - // do not insert extra line break following a /*-style comment - // before the closing '}' as it might break the code if there - // is no trailing ',' - mode := noExtraLinebreak - // do not insert extra blank following a /*-style comment - // before the closing '}' unless the literal is empty - if len(x.Elts) > 0 { - mode |= noExtraBlank - } - // need the initial indent to print lone comments with - // the proper level of indentation - p.print(indent, unindent, mode, x.Rbrace, token.RBRACE, mode) - p.level-- - - case *ast.Ellipsis: - p.print(token.ELLIPSIS) - if x.Elt != nil { - p.expr(x.Elt) - } - - case *ast.ArrayType: - p.print(token.LBRACK) - if x.Len != nil { - p.expr(x.Len) - } - p.print(token.RBRACK) - p.expr(x.Elt) - - case *ast.StructType: - p.print(token.STRUCT) - p.fieldList(x.Fields, true, x.Incomplete) - - case *ast.FuncType: - p.print(token.FUNC) - p.signature(x) - - case *ast.InterfaceType: - p.print(token.INTERFACE) - p.fieldList(x.Methods, false, x.Incomplete) - - case *ast.MapType: - p.print(token.MAP, token.LBRACK) - p.expr(x.Key) - p.print(token.RBRACK) - p.expr(x.Value) - - case *ast.ChanType: - switch x.Dir { - case ast.SEND | ast.RECV: - p.print(token.CHAN) - case ast.RECV: - p.print(token.ARROW, token.CHAN) // x.Arrow and x.Pos() are the same - case ast.SEND: - p.print(token.CHAN, x.Arrow, token.ARROW) - } - p.print(blank) - p.expr(x.Value) - - default: - panic("unreachable") - } -} - -// normalizedNumber rewrites base prefixes and exponents -// of numbers to use lower-case letters (0X123 to 0x123 and 1.2E3 to 1.2e3), -// and removes leading 0's from integer imaginary literals (0765i to 765i). -// It leaves hexadecimal digits alone. -// -// normalizedNumber doesn't modify the ast.BasicLit value lit points to. -// If lit is not a number or a number in canonical format already, -// lit is returned as is. Otherwise a new ast.BasicLit is created. -func normalizedNumber(lit *ast.BasicLit) *ast.BasicLit { - if lit.Kind != token.INT && lit.Kind != token.FLOAT && lit.Kind != token.IMAG { - return lit // not a number - nothing to do - } - if len(lit.Value) < 2 { - return lit // only one digit (common case) - nothing to do - } - // len(lit.Value) >= 2 - - // We ignore lit.Kind because for lit.Kind == token.IMAG the literal may be an integer - // or floating-point value, decimal or not. Instead, just consider the literal pattern. - x := lit.Value - switch x[:2] { - default: - // 0-prefix octal, decimal int, or float (possibly with 'i' suffix) - if i := strings.LastIndexByte(x, 'E'); i >= 0 { - x = x[:i] + "e" + x[i+1:] - break - } - // remove leading 0's from integer (but not floating-point) imaginary literals - if x[len(x)-1] == 'i' && !strings.ContainsAny(x, ".e") { - x = strings.TrimLeft(x, "0_") - if x == "i" { - x = "0i" - } - } - case "0X": - x = "0x" + x[2:] - // possibly a hexadecimal float - if i := strings.LastIndexByte(x, 'P'); i >= 0 { - x = x[:i] + "p" + x[i+1:] - } - case "0x": - // possibly a hexadecimal float - i := strings.LastIndexByte(x, 'P') - if i == -1 { - return lit // nothing to do - } - x = x[:i] + "p" + x[i+1:] - case "0O": - x = "0o" + x[2:] - case "0o": - return lit // nothing to do - case "0B": - x = "0b" + x[2:] - case "0b": - return lit // nothing to do - } - - return &ast.BasicLit{ValuePos: lit.ValuePos, Kind: lit.Kind, Value: x} -} - -func (p *printer) possibleSelectorExpr(expr ast.Expr, prec1, depth int) bool { - if x, ok := expr.(*ast.SelectorExpr); ok { - return p.selectorExpr(x, depth, true) - } - p.expr1(expr, prec1, depth) - return false -} - -// selectorExpr handles an *ast.SelectorExpr node and reports whether x spans -// multiple lines. -func (p *printer) selectorExpr(x *ast.SelectorExpr, depth int, isMethod bool) bool { - p.expr1(x.X, token.HighestPrec, depth) - p.print(token.PERIOD) - if line := p.lineFor(x.Sel.Pos()); p.pos.IsValid() && p.pos.Line < line { - p.print(indent, newline, x.Sel.Pos(), x.Sel) - if !isMethod { - p.print(unindent) - } - return true - } - p.print(x.Sel.Pos(), x.Sel) - return false -} - -func (p *printer) expr0(x ast.Expr, depth int) { - p.expr1(x, token.LowestPrec, depth) -} - -func (p *printer) expr(x ast.Expr) { - const depth = 1 - p.expr1(x, token.LowestPrec, depth) -} - -// ---------------------------------------------------------------------------- -// Statements - -// Print the statement list indented, but without a newline after the last statement. -// Extra line breaks between statements in the source are respected but at most one -// empty line is printed between statements. -func (p *printer) stmtList(list []ast.Stmt, nindent int, nextIsRBrace bool) { - if nindent > 0 { - p.print(indent) - } - var line int - i := 0 - for _, s := range list { - // ignore empty statements (was issue 3466) - if _, isEmpty := s.(*ast.EmptyStmt); !isEmpty { - // nindent == 0 only for lists of switch/select case clauses; - // in those cases each clause is a new section - if len(p.output) > 0 { - // only print line break if we are not at the beginning of the output - // (i.e., we are not printing only a partial program) - p.linebreak(p.lineFor(s.Pos()), 1, ignore, i == 0 || nindent == 0 || p.linesFrom(line) > 0) - } - p.recordLine(&line) - p.stmt(s, nextIsRBrace && i == len(list)-1) - // labeled statements put labels on a separate line, but here - // we only care about the start line of the actual statement - // without label - correct line for each label - for t := s; ; { - lt, _ := t.(*ast.LabeledStmt) - if lt == nil { - break - } - line++ - t = lt.Stmt - } - i++ - } - } - if nindent > 0 { - p.print(unindent) - } -} - -// block prints an *ast.BlockStmt; it always spans at least two lines. -func (p *printer) block(b *ast.BlockStmt, nindent int) { - p.print(b.Lbrace, token.LBRACE) - p.stmtList(b.List, nindent, true) - p.linebreak(p.lineFor(b.Rbrace), 1, ignore, true) - p.print(b.Rbrace, token.RBRACE) -} - -func isTypeName(x ast.Expr) bool { - switch t := x.(type) { - case *ast.Ident: - return true - case *ast.SelectorExpr: - return isTypeName(t.X) - } - return false -} - -func stripParens(x ast.Expr) ast.Expr { - if px, strip := x.(*ast.ParenExpr); strip { - // parentheses must not be stripped if there are any - // unparenthesized composite literals starting with - // a type name - ast.Inspect(px.X, func(node ast.Node) bool { - switch x := node.(type) { - case *ast.ParenExpr: - // parentheses protect enclosed composite literals - return false - case *ast.CompositeLit: - if isTypeName(x.Type) { - strip = false // do not strip parentheses - } - return false - } - // in all other cases, keep inspecting - return true - }) - if strip { - return stripParens(px.X) - } - } - return x -} - -func stripParensAlways(x ast.Expr) ast.Expr { - if x, ok := x.(*ast.ParenExpr); ok { - return stripParensAlways(x.X) - } - return x -} - -func (p *printer) controlClause(isForStmt bool, init ast.Stmt, expr ast.Expr, post ast.Stmt) { - p.print(blank) - needsBlank := false - if init == nil && post == nil { - // no semicolons required - if expr != nil { - p.expr(stripParens(expr)) - needsBlank = true - } - } else { - // all semicolons required - // (they are not separators, print them explicitly) - if init != nil { - p.stmt(init, false) - } - p.print(token.SEMICOLON, blank) - if expr != nil { - p.expr(stripParens(expr)) - needsBlank = true - } - if isForStmt { - p.print(token.SEMICOLON, blank) - needsBlank = false - if post != nil { - p.stmt(post, false) - needsBlank = true - } - } - } - if needsBlank { - p.print(blank) - } -} - -// indentList reports whether an expression list would look better if it -// were indented wholesale (starting with the very first element, rather -// than starting at the first line break). -func (p *printer) indentList(list []ast.Expr) bool { - // Heuristic: indentList reports whether there are more than one multi- - // line element in the list, or if there is any element that is not - // starting on the same line as the previous one ends. - if len(list) >= 2 { - var b = p.lineFor(list[0].Pos()) - var e = p.lineFor(list[len(list)-1].End()) - if 0 < b && b < e { - // list spans multiple lines - n := 0 // multi-line element count - line := b - for _, x := range list { - xb := p.lineFor(x.Pos()) - xe := p.lineFor(x.End()) - if line < xb { - // x is not starting on the same - // line as the previous one ended - return true - } - if xb < xe { - // x is a multi-line element - n++ - } - line = xe - } - return n > 1 - } - } - return false -} - -func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool) { - p.print(stmt.Pos()) - - switch s := stmt.(type) { - case *ast.BadStmt: - p.print("BadStmt") - - case *ast.DeclStmt: - p.decl(s.Decl) - - case *ast.EmptyStmt: - // nothing to do - - case *ast.LabeledStmt: - // a "correcting" unindent immediately following a line break - // is applied before the line break if there is no comment - // between (see writeWhitespace) - p.print(unindent) - p.expr(s.Label) - p.print(s.Colon, token.COLON, indent) - if e, isEmpty := s.Stmt.(*ast.EmptyStmt); isEmpty { - if !nextIsRBrace { - p.print(newline, e.Pos(), token.SEMICOLON) - break - } - } else { - p.linebreak(p.lineFor(s.Stmt.Pos()), 1, ignore, true) - } - p.stmt(s.Stmt, nextIsRBrace) - - case *ast.ExprStmt: - const depth = 1 - p.expr0(s.X, depth) - - case *ast.SendStmt: - const depth = 1 - p.expr0(s.Chan, depth) - p.print(blank, s.Arrow, token.ARROW, blank) - p.expr0(s.Value, depth) - - case *ast.IncDecStmt: - const depth = 1 - p.expr0(s.X, depth+1) - p.print(s.TokPos, s.Tok) - - case *ast.AssignStmt: - var depth = 1 - if len(s.Lhs) > 1 && len(s.Rhs) > 1 { - depth++ - } - p.exprList(s.Pos(), s.Lhs, depth, 0, s.TokPos, false) - p.print(blank, s.TokPos, s.Tok, blank) - p.exprList(s.TokPos, s.Rhs, depth, 0, token.NoPos, false) - - case *ast.GoStmt: - p.print(token.GO, blank) - p.expr(s.Call) - - case *ast.DeferStmt: - p.print(token.DEFER, blank) - p.expr(s.Call) - - case *ast.ReturnStmt: - p.print(token.RETURN) - if s.Results != nil { - p.print(blank) - // Use indentList heuristic to make corner cases look - // better (issue 1207). A more systematic approach would - // always indent, but this would cause significant - // reformatting of the code base and not necessarily - // lead to more nicely formatted code in general. - if p.indentList(s.Results) { - p.print(indent) - // Use NoPos so that a newline never goes before - // the results (see issue #32854). - p.exprList(token.NoPos, s.Results, 1, noIndent, token.NoPos, false) - p.print(unindent) - } else { - p.exprList(token.NoPos, s.Results, 1, 0, token.NoPos, false) - } - } - - case *ast.BranchStmt: - p.print(s.Tok) - if s.Label != nil { - p.print(blank) - p.expr(s.Label) - } - - case *ast.BlockStmt: - p.block(s, 1) - - case *ast.IfStmt: - p.print(token.IF) - p.controlClause(false, s.Init, s.Cond, nil) - p.block(s.Body, 1) - if s.Else != nil { - p.print(blank, token.ELSE, blank) - switch s.Else.(type) { - case *ast.BlockStmt, *ast.IfStmt: - p.stmt(s.Else, nextIsRBrace) - default: - // This can only happen with an incorrectly - // constructed AST. Permit it but print so - // that it can be parsed without errors. - p.print(token.LBRACE, indent, formfeed) - p.stmt(s.Else, true) - p.print(unindent, formfeed, token.RBRACE) - } - } - - case *ast.CaseClause: - if s.List != nil { - p.print(token.CASE, blank) - p.exprList(s.Pos(), s.List, 1, 0, s.Colon, false) - } else { - p.print(token.DEFAULT) - } - p.print(s.Colon, token.COLON) - p.stmtList(s.Body, 1, nextIsRBrace) - - case *ast.SwitchStmt: - p.print(token.SWITCH) - p.controlClause(false, s.Init, s.Tag, nil) - p.block(s.Body, 0) - - case *ast.TypeSwitchStmt: - p.print(token.SWITCH) - if s.Init != nil { - p.print(blank) - p.stmt(s.Init, false) - p.print(token.SEMICOLON) - } - p.print(blank) - p.stmt(s.Assign, false) - p.print(blank) - p.block(s.Body, 0) - - case *ast.CommClause: - if s.Comm != nil { - p.print(token.CASE, blank) - p.stmt(s.Comm, false) - } else { - p.print(token.DEFAULT) - } - p.print(s.Colon, token.COLON) - p.stmtList(s.Body, 1, nextIsRBrace) - - case *ast.SelectStmt: - p.print(token.SELECT, blank) - body := s.Body - if len(body.List) == 0 && !p.commentBefore(p.posFor(body.Rbrace)) { - // print empty select statement w/o comments on one line - p.print(body.Lbrace, token.LBRACE, body.Rbrace, token.RBRACE) - } else { - p.block(body, 0) - } - - case *ast.ForStmt: - p.print(token.FOR) - p.controlClause(true, s.Init, s.Cond, s.Post) - p.block(s.Body, 1) - - case *ast.RangeStmt: - p.print(token.FOR, blank) - if s.Key != nil { - p.expr(s.Key) - if s.Value != nil { - // use position of value following the comma as - // comma position for correct comment placement - p.print(s.Value.Pos(), token.COMMA, blank) - p.expr(s.Value) - } - p.print(blank, s.TokPos, s.Tok, blank) - } - p.print(token.RANGE, blank) - p.expr(stripParens(s.X)) - p.print(blank) - p.block(s.Body, 1) - - default: - panic("unreachable") - } -} - -// ---------------------------------------------------------------------------- -// Declarations - -// The keepTypeColumn function determines if the type column of a series of -// consecutive const or var declarations must be kept, or if initialization -// values (V) can be placed in the type column (T) instead. The i'th entry -// in the result slice is true if the type column in spec[i] must be kept. -// -// For example, the declaration: -// -// const ( -// foobar int = 42 // comment -// x = 7 // comment -// foo -// bar = 991 -// ) -// -// leads to the type/values matrix below. A run of value columns (V) can -// be moved into the type column if there is no type for any of the values -// in that column (we only move entire columns so that they align properly). -// -// matrix formatted result -// matrix -// T V -> T V -> true there is a T and so the type -// - V - V true column must be kept -// - - - - false -// - V V - false V is moved into T column -func keepTypeColumn(specs []ast.Spec) []bool { - m := make([]bool, len(specs)) - - populate := func(i, j int, keepType bool) { - if keepType { - for ; i < j; i++ { - m[i] = true - } - } - } - - i0 := -1 // if i0 >= 0 we are in a run and i0 is the start of the run - var keepType bool - for i, s := range specs { - t := s.(*ast.ValueSpec) - if t.Values != nil { - if i0 < 0 { - // start of a run of ValueSpecs with non-nil Values - i0 = i - keepType = false - } - } else { - if i0 >= 0 { - // end of a run - populate(i0, i, keepType) - i0 = -1 - } - } - if t.Type != nil { - keepType = true - } - } - if i0 >= 0 { - // end of a run - populate(i0, len(specs), keepType) - } - - return m -} - -func (p *printer) valueSpec(s *ast.ValueSpec, keepType bool) { - p.setComment(s.Doc) - p.identList(s.Names, false) // always present - extraTabs := 3 - if s.Type != nil || keepType { - p.print(vtab) - extraTabs-- - } - if s.Type != nil { - p.expr(s.Type) - } - if s.Values != nil { - p.print(vtab, token.ASSIGN, blank) - p.exprList(token.NoPos, s.Values, 1, 0, token.NoPos, false) - extraTabs-- - } - if s.Comment != nil { - for ; extraTabs > 0; extraTabs-- { - p.print(vtab) - } - p.setComment(s.Comment) - } -} - -func sanitizeImportPath(lit *ast.BasicLit) *ast.BasicLit { - // Note: An unmodified AST generated by go/parser will already - // contain a backward- or double-quoted path string that does - // not contain any invalid characters, and most of the work - // here is not needed. However, a modified or generated AST - // may possibly contain non-canonical paths. Do the work in - // all cases since it's not too hard and not speed-critical. - - // if we don't have a proper string, be conservative and return whatever we have - if lit.Kind != token.STRING { - return lit - } - s, err := strconv.Unquote(lit.Value) - if err != nil { - return lit - } - - // if the string is an invalid path, return whatever we have - // - // spec: "Implementation restriction: A compiler may restrict - // ImportPaths to non-empty strings using only characters belonging - // to Unicode's L, M, N, P, and S general categories (the Graphic - // characters without spaces) and may also exclude the characters - // !"#$%&'()*,:;<=>?[\]^`{|} and the Unicode replacement character - // U+FFFD." - if s == "" { - return lit - } - const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD" - for _, r := range s { - if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) { - return lit - } - } - - // otherwise, return the double-quoted path - s = strconv.Quote(s) - if s == lit.Value { - return lit // nothing wrong with lit - } - return &ast.BasicLit{ValuePos: lit.ValuePos, Kind: token.STRING, Value: s} -} - -// The parameter n is the number of specs in the group. If doIndent is set, -// multi-line identifier lists in the spec are indented when the first -// linebreak is encountered. -func (p *printer) spec(spec ast.Spec, n int, doIndent bool) { - switch s := spec.(type) { - case *ast.ImportSpec: - p.setComment(s.Doc) - if s.Name != nil { - p.expr(s.Name) - p.print(blank) - } - p.expr(sanitizeImportPath(s.Path)) - p.setComment(s.Comment) - p.print(s.EndPos) - - case *ast.ValueSpec: - if n != 1 { - p.internalError("expected n = 1; got", n) - } - p.setComment(s.Doc) - p.identList(s.Names, doIndent) // always present - if s.Type != nil { - p.print(blank) - p.expr(s.Type) - } - if s.Values != nil { - p.print(blank, token.ASSIGN, blank) - p.exprList(token.NoPos, s.Values, 1, 0, token.NoPos, false) - } - p.setComment(s.Comment) - - case *ast.TypeSpec: - p.setComment(s.Doc) - p.expr(s.Name) - if s.TypeParams != nil { - p.parameters(s.TypeParams, typeTParam) - } - if n == 1 { - p.print(blank) - } else { - p.print(vtab) - } - if s.Assign.IsValid() { - p.print(token.ASSIGN, blank) - } - p.expr(s.Type) - p.setComment(s.Comment) - - default: - panic("unreachable") - } -} - -func (p *printer) genDecl(d *ast.GenDecl) { - p.setComment(d.Doc) - p.print(d.Pos(), d.Tok, blank) - - if d.Lparen.IsValid() || len(d.Specs) > 1 { - // group of parenthesized declarations - p.print(d.Lparen, token.LPAREN) - if n := len(d.Specs); n > 0 { - p.print(indent, formfeed) - if n > 1 && (d.Tok == token.CONST || d.Tok == token.VAR) { - // two or more grouped const/var declarations: - // determine if the type column must be kept - keepType := keepTypeColumn(d.Specs) - var line int - for i, s := range d.Specs { - if i > 0 { - p.linebreak(p.lineFor(s.Pos()), 1, ignore, p.linesFrom(line) > 0) - } - p.recordLine(&line) - p.valueSpec(s.(*ast.ValueSpec), keepType[i]) - } - } else { - var line int - for i, s := range d.Specs { - if i > 0 { - p.linebreak(p.lineFor(s.Pos()), 1, ignore, p.linesFrom(line) > 0) - } - p.recordLine(&line) - p.spec(s, n, false) - } - } - p.print(unindent, formfeed) - } - p.print(d.Rparen, token.RPAREN) - - } else if len(d.Specs) > 0 { - // single declaration - p.spec(d.Specs[0], 1, true) - } -} - -// nodeSize determines the size of n in chars after formatting. -// The result is <= maxSize if the node fits on one line with at -// most maxSize chars and the formatted output doesn't contain -// any control chars. Otherwise, the result is > maxSize. -func (p *printer) nodeSize(n ast.Node, maxSize int) (size int) { - // nodeSize invokes the printer, which may invoke nodeSize - // recursively. For deep composite literal nests, this can - // lead to an exponential algorithm. Remember previous - // results to prune the recursion (was issue 1628). - if size, found := p.nodeSizes[n]; found { - return size - } - - size = maxSize + 1 // assume n doesn't fit - p.nodeSizes[n] = size - - // nodeSize computation must be independent of particular - // style so that we always get the same decision; print - // in RawFormat - cfg := Config{Mode: RawFormat} - var buf bytes.Buffer - if err := cfg.fprint(&buf, p.fset, n, p.nodeSizes); err != nil { - return - } - if buf.Len() <= maxSize { - for _, ch := range buf.Bytes() { - switch ch { - case '\n', '\f': - return // does not fit in a single line - } - } - size = buf.Len() // n fits - p.nodeSizes[n] = size - } - return -} - -// numLines returns the number of lines spanned by node n in the original source. -func (p *printer) numLines(n ast.Node) int { - if from := n.Pos(); from.IsValid() { - if to := n.End(); to.IsValid() { - return p.lineFor(to) - p.lineFor(from) + 1 - } - } - return infinity -} - -// bodySize is like nodeSize but it is specialized for *ast.BlockStmt's. -func (p *printer) bodySize(b *ast.BlockStmt, maxSize int) int { - pos1 := b.Pos() - pos2 := b.Rbrace - if pos1.IsValid() && pos2.IsValid() && p.lineFor(pos1) != p.lineFor(pos2) { - // opening and closing brace are on different lines - don't make it a one-liner - return maxSize + 1 - } - if len(b.List) > 5 { - // too many statements - don't make it a one-liner - return maxSize + 1 - } - // otherwise, estimate body size - bodySize := p.commentSizeBefore(p.posFor(pos2)) - for i, s := range b.List { - if bodySize > maxSize { - break // no need to continue - } - if i > 0 { - bodySize += 2 // space for a semicolon and blank - } - bodySize += p.nodeSize(s, maxSize) - } - return bodySize -} - -// funcBody prints a function body following a function header of given headerSize. -// If the header's and block's size are "small enough" and the block is "simple enough", -// the block is printed on the current line, without line breaks, spaced from the header -// by sep. Otherwise the block's opening "{" is printed on the current line, followed by -// lines for the block's statements and its closing "}". -func (p *printer) funcBody(headerSize int, sep whiteSpace, b *ast.BlockStmt) { - if b == nil { - return - } - - // save/restore composite literal nesting level - defer func(level int) { - p.level = level - }(p.level) - p.level = 0 - - const maxSize = 100 - if headerSize+p.bodySize(b, maxSize) <= maxSize { - p.print(sep, b.Lbrace, token.LBRACE) - if len(b.List) > 0 { - p.print(blank) - for i, s := range b.List { - if i > 0 { - p.print(token.SEMICOLON, blank) - } - p.stmt(s, i == len(b.List)-1) - } - p.print(blank) - } - p.print(noExtraLinebreak, b.Rbrace, token.RBRACE, noExtraLinebreak) - return - } - - if sep != ignore { - p.print(blank) // always use blank - } - p.block(b, 1) -} - -// distanceFrom returns the column difference between p.out (the current output -// position) and startOutCol. If the start position is on a different line from -// the current position (or either is unknown), the result is infinity. -func (p *printer) distanceFrom(startPos token.Pos, startOutCol int) int { - if startPos.IsValid() && p.pos.IsValid() && p.posFor(startPos).Line == p.pos.Line { - return p.out.Column - startOutCol - } - return infinity -} - -func (p *printer) funcDecl(d *ast.FuncDecl) { - p.setComment(d.Doc) - p.print(d.Pos(), token.FUNC, blank) - // We have to save startCol only after emitting FUNC; otherwise it can be on a - // different line (all whitespace preceding the FUNC is emitted only when the - // FUNC is emitted). - startCol := p.out.Column - len("func ") - if d.Recv != nil { - p.parameters(d.Recv, funcParam) // method: print receiver - p.print(blank) - } - p.expr(d.Name) - p.signature(d.Type) - p.funcBody(p.distanceFrom(d.Pos(), startCol), vtab, d.Body) -} - -func (p *printer) decl(decl ast.Decl) { - switch d := decl.(type) { - case *ast.BadDecl: - p.print(d.Pos(), "BadDecl") - case *ast.GenDecl: - p.genDecl(d) - case *ast.FuncDecl: - p.funcDecl(d) - default: - panic("unreachable") - } -} - -// ---------------------------------------------------------------------------- -// Files - -func declToken(decl ast.Decl) (tok token.Token) { - tok = token.ILLEGAL - switch d := decl.(type) { - case *ast.GenDecl: - tok = d.Tok - case *ast.FuncDecl: - tok = token.FUNC - } - return -} - -func (p *printer) declList(list []ast.Decl) { - tok := token.ILLEGAL - for _, d := range list { - prev := tok - tok = declToken(d) - // If the declaration token changed (e.g., from CONST to TYPE) - // or the next declaration has documentation associated with it, - // print an empty line between top-level declarations. - // (because p.linebreak is called with the position of d, which - // is past any documentation, the minimum requirement is satisfied - // even w/o the extra getDoc(d) nil-check - leave it in case the - // linebreak logic improves - there's already a TODO). - if len(p.output) > 0 { - // only print line break if we are not at the beginning of the output - // (i.e., we are not printing only a partial program) - min := 1 - if prev != tok || getDoc(d) != nil { - min = 2 - } - // start a new section if the next declaration is a function - // that spans multiple lines (see also issue #19544) - p.linebreak(p.lineFor(d.Pos()), min, ignore, tok == token.FUNC && p.numLines(d) > 1) - } - p.decl(d) - } -} - -func (p *printer) file(src *ast.File) { - p.setComment(src.Doc) - p.print(src.Pos(), token.PACKAGE, blank) - p.expr(src.Name) - p.declList(src.Decls) - p.print(newline) -} diff --git a/internal/backport/go/printer/performance_test.go b/internal/backport/go/printer/performance_test.go deleted file mode 100644 index bc8b17b382..0000000000 --- a/internal/backport/go/printer/performance_test.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2009 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. - -// This file implements a simple printer performance benchmark: -// go test -bench=BenchmarkPrint - -package printer - -import ( - "bytes" - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/parser" - "io" - "log" - "os" - "testing" -) - -var ( - testfile *ast.File - testsize int64 -) - -func testprint(out io.Writer, file *ast.File) { - if err := (&Config{TabIndent | UseSpaces | normalizeNumbers, 8, 0}).Fprint(out, fset, file); err != nil { - log.Fatalf("print error: %s", err) - } -} - -// cannot initialize in init because (printer) Fprint launches goroutines. -func initialize() { - const filename = "testdata/parser.go" - - src, err := os.ReadFile(filename) - if err != nil { - log.Fatalf("%s", err) - } - - file, err := parser.ParseFile(fset, filename, src, parser.ParseComments) - if err != nil { - log.Fatalf("%s", err) - } - - var buf bytes.Buffer - testprint(&buf, file) - if !bytes.Equal(buf.Bytes(), src) { - log.Fatalf("print error: %s not idempotent", filename) - } - - testfile = file - testsize = int64(len(src)) -} - -func BenchmarkPrint(b *testing.B) { - if testfile == nil { - initialize() - } - b.ReportAllocs() - b.SetBytes(testsize) - for i := 0; i < b.N; i++ { - testprint(io.Discard, testfile) - } -} diff --git a/internal/backport/go/printer/printer.go b/internal/backport/go/printer/printer.go deleted file mode 100644 index 0ab4b96a91..0000000000 --- a/internal/backport/go/printer/printer.go +++ /dev/null @@ -1,1409 +0,0 @@ -// Copyright 2009 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 printer implements printing of AST nodes. -package printer - -import ( - "fmt" - "go/build/constraint" - "io" - "os" - "strings" - "text/tabwriter" - "unicode" - - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/token" -) - -const ( - maxNewlines = 2 // max. number of newlines between source text - debug = false // enable for debugging - infinity = 1 << 30 -) - -type whiteSpace byte - -const ( - ignore = whiteSpace(0) - blank = whiteSpace(' ') - vtab = whiteSpace('\v') - newline = whiteSpace('\n') - formfeed = whiteSpace('\f') - indent = whiteSpace('>') - unindent = whiteSpace('<') -) - -// A pmode value represents the current printer mode. -type pmode int - -const ( - noExtraBlank pmode = 1 << iota // disables extra blank after /*-style comment - noExtraLinebreak // disables extra line break after /*-style comment -) - -type commentInfo struct { - cindex int // current comment index - comment *ast.CommentGroup // = printer.comments[cindex]; or nil - commentOffset int // = printer.posFor(printer.comments[cindex].List[0].Pos()).Offset; or infinity - commentNewline bool // true if the comment group contains newlines -} - -type printer struct { - // Configuration (does not change after initialization) - Config - fset *token.FileSet - - // Current state - output []byte // raw printer result - indent int // current indentation - level int // level == 0: outside composite literal; level > 0: inside composite literal - mode pmode // current printer mode - endAlignment bool // if set, terminate alignment immediately - impliedSemi bool // if set, a linebreak implies a semicolon - lastTok token.Token // last token printed (token.ILLEGAL if it's whitespace) - prevOpen token.Token // previous non-brace "open" token (, [, or token.ILLEGAL - wsbuf []whiteSpace // delayed white space - goBuild []int // start index of all //go:build comments in output - plusBuild []int // start index of all // +build comments in output - - // Positions - // The out position differs from the pos position when the result - // formatting differs from the source formatting (in the amount of - // white space). If there's a difference and SourcePos is set in - // ConfigMode, //line directives are used in the output to restore - // original source positions for a reader. - pos token.Position // current position in AST (source) space - out token.Position // current position in output space - last token.Position // value of pos after calling writeString - linePtr *int // if set, record out.Line for the next token in *linePtr - - // The list of all source comments, in order of appearance. - comments []*ast.CommentGroup // may be nil - useNodeComments bool // if not set, ignore lead and line comments of nodes - - // Information about p.comments[p.cindex]; set up by nextComment. - commentInfo - - // Cache of already computed node sizes. - nodeSizes map[ast.Node]int - - // Cache of most recently computed line position. - cachedPos token.Pos - cachedLine int // line corresponding to cachedPos -} - -func (p *printer) init(cfg *Config, fset *token.FileSet, nodeSizes map[ast.Node]int) { - p.Config = *cfg - p.fset = fset - p.pos = token.Position{Line: 1, Column: 1} - p.out = token.Position{Line: 1, Column: 1} - p.wsbuf = make([]whiteSpace, 0, 16) // whitespace sequences are short - p.nodeSizes = nodeSizes - p.cachedPos = -1 -} - -func (p *printer) internalError(msg ...interface{}) { - if debug { - fmt.Print(p.pos.String() + ": ") - fmt.Println(msg...) - panic("golang.org/x/website/internal/backport/go/printer") - } -} - -// commentsHaveNewline reports whether a list of comments belonging to -// an *ast.CommentGroup contains newlines. Because the position information -// may only be partially correct, we also have to read the comment text. -func (p *printer) commentsHaveNewline(list []*ast.Comment) bool { - // len(list) > 0 - line := p.lineFor(list[0].Pos()) - for i, c := range list { - if i > 0 && p.lineFor(list[i].Pos()) != line { - // not all comments on the same line - return true - } - if t := c.Text; len(t) >= 2 && (t[1] == '/' || strings.Contains(t, "\n")) { - return true - } - } - _ = line - return false -} - -func (p *printer) nextComment() { - for p.cindex < len(p.comments) { - c := p.comments[p.cindex] - p.cindex++ - if list := c.List; len(list) > 0 { - p.comment = c - p.commentOffset = p.posFor(list[0].Pos()).Offset - p.commentNewline = p.commentsHaveNewline(list) - return - } - // we should not reach here (correct ASTs don't have empty - // ast.CommentGroup nodes), but be conservative and try again - } - // no more comments - p.commentOffset = infinity -} - -// commentBefore reports whether the current comment group occurs -// before the next position in the source code and printing it does -// not introduce implicit semicolons. -func (p *printer) commentBefore(next token.Position) bool { - return p.commentOffset < next.Offset && (!p.impliedSemi || !p.commentNewline) -} - -// commentSizeBefore returns the estimated size of the -// comments on the same line before the next position. -func (p *printer) commentSizeBefore(next token.Position) int { - // save/restore current p.commentInfo (p.nextComment() modifies it) - defer func(info commentInfo) { - p.commentInfo = info - }(p.commentInfo) - - size := 0 - for p.commentBefore(next) { - for _, c := range p.comment.List { - size += len(c.Text) - } - p.nextComment() - } - return size -} - -// recordLine records the output line number for the next non-whitespace -// token in *linePtr. It is used to compute an accurate line number for a -// formatted construct, independent of pending (not yet emitted) whitespace -// or comments. -func (p *printer) recordLine(linePtr *int) { - p.linePtr = linePtr -} - -// linesFrom returns the number of output lines between the current -// output line and the line argument, ignoring any pending (not yet -// emitted) whitespace or comments. It is used to compute an accurate -// size (in number of lines) for a formatted construct. -func (p *printer) linesFrom(line int) int { - return p.out.Line - line -} - -func (p *printer) posFor(pos token.Pos) token.Position { - // not used frequently enough to cache entire token.Position - return p.fset.PositionFor(pos, false /* absolute position */) -} - -func (p *printer) lineFor(pos token.Pos) int { - if pos != p.cachedPos { - p.cachedPos = pos - p.cachedLine = p.fset.PositionFor(pos, false /* absolute position */).Line - } - return p.cachedLine -} - -// writeLineDirective writes a //line directive if necessary. -func (p *printer) writeLineDirective(pos token.Position) { - if pos.IsValid() && (p.out.Line != pos.Line || p.out.Filename != pos.Filename) { - p.output = append(p.output, tabwriter.Escape) // protect '\n' in //line from tabwriter interpretation - p.output = append(p.output, fmt.Sprintf("//line %s:%d\n", pos.Filename, pos.Line)...) - p.output = append(p.output, tabwriter.Escape) - // p.out must match the //line directive - p.out.Filename = pos.Filename - p.out.Line = pos.Line - } -} - -// writeIndent writes indentation. -func (p *printer) writeIndent() { - // use "hard" htabs - indentation columns - // must not be discarded by the tabwriter - n := p.Config.Indent + p.indent // include base indentation - for i := 0; i < n; i++ { - p.output = append(p.output, '\t') - } - - // update positions - p.pos.Offset += n - p.pos.Column += n - p.out.Column += n -} - -// writeByte writes ch n times to p.output and updates p.pos. -// Only used to write formatting (white space) characters. -func (p *printer) writeByte(ch byte, n int) { - if p.endAlignment { - // Ignore any alignment control character; - // and at the end of the line, break with - // a formfeed to indicate termination of - // existing columns. - switch ch { - case '\t', '\v': - ch = ' ' - case '\n', '\f': - ch = '\f' - p.endAlignment = false - } - } - - if p.out.Column == 1 { - // no need to write line directives before white space - p.writeIndent() - } - - for i := 0; i < n; i++ { - p.output = append(p.output, ch) - } - - // update positions - p.pos.Offset += n - if ch == '\n' || ch == '\f' { - p.pos.Line += n - p.out.Line += n - p.pos.Column = 1 - p.out.Column = 1 - return - } - p.pos.Column += n - p.out.Column += n -} - -// writeString writes the string s to p.output and updates p.pos, p.out, -// and p.last. If isLit is set, s is escaped w/ tabwriter.Escape characters -// to protect s from being interpreted by the tabwriter. -// -// Note: writeString is only used to write Go tokens, literals, and -// comments, all of which must be written literally. Thus, it is correct -// to always set isLit = true. However, setting it explicitly only when -// needed (i.e., when we don't know that s contains no tabs or line breaks) -// avoids processing extra escape characters and reduces run time of the -// printer benchmark by up to 10%. -func (p *printer) writeString(pos token.Position, s string, isLit bool) { - if p.out.Column == 1 { - if p.Config.Mode&SourcePos != 0 { - p.writeLineDirective(pos) - } - p.writeIndent() - } - - if pos.IsValid() { - // update p.pos (if pos is invalid, continue with existing p.pos) - // Note: Must do this after handling line beginnings because - // writeIndent updates p.pos if there's indentation, but p.pos - // is the position of s. - p.pos = pos - } - - if isLit { - // Protect s such that is passes through the tabwriter - // unchanged. Note that valid Go programs cannot contain - // tabwriter.Escape bytes since they do not appear in legal - // UTF-8 sequences. - p.output = append(p.output, tabwriter.Escape) - } - - if debug { - p.output = append(p.output, fmt.Sprintf("/*%s*/", pos)...) // do not update p.pos! - } - p.output = append(p.output, s...) - - // update positions - nlines := 0 - var li int // index of last newline; valid if nlines > 0 - for i := 0; i < len(s); i++ { - // Raw string literals may contain any character except back quote (`). - if ch := s[i]; ch == '\n' || ch == '\f' { - // account for line break - nlines++ - li = i - // A line break inside a literal will break whatever column - // formatting is in place; ignore any further alignment through - // the end of the line. - p.endAlignment = true - } - } - p.pos.Offset += len(s) - if nlines > 0 { - p.pos.Line += nlines - p.out.Line += nlines - c := len(s) - li - p.pos.Column = c - p.out.Column = c - } else { - p.pos.Column += len(s) - p.out.Column += len(s) - } - - if isLit { - p.output = append(p.output, tabwriter.Escape) - } - - p.last = p.pos -} - -// writeCommentPrefix writes the whitespace before a comment. -// If there is any pending whitespace, it consumes as much of -// it as is likely to help position the comment nicely. -// pos is the comment position, next the position of the item -// after all pending comments, prev is the previous comment in -// a group of comments (or nil), and tok is the next token. -func (p *printer) writeCommentPrefix(pos, next token.Position, prev *ast.Comment, tok token.Token) { - if len(p.output) == 0 { - // the comment is the first item to be printed - don't write any whitespace - return - } - - if pos.IsValid() && pos.Filename != p.last.Filename { - // comment in a different file - separate with newlines - p.writeByte('\f', maxNewlines) - return - } - - if pos.Line == p.last.Line && (prev == nil || prev.Text[1] != '/') { - // comment on the same line as last item: - // separate with at least one separator - hasSep := false - if prev == nil { - // first comment of a comment group - j := 0 - for i, ch := range p.wsbuf { - switch ch { - case blank: - // ignore any blanks before a comment - p.wsbuf[i] = ignore - continue - case vtab: - // respect existing tabs - important - // for proper formatting of commented structs - hasSep = true - continue - case indent: - // apply pending indentation - continue - } - j = i - break - } - p.writeWhitespace(j) - } - // make sure there is at least one separator - if !hasSep { - sep := byte('\t') - if pos.Line == next.Line { - // next item is on the same line as the comment - // (which must be a /*-style comment): separate - // with a blank instead of a tab - sep = ' ' - } - p.writeByte(sep, 1) - } - - } else { - // comment on a different line: - // separate with at least one line break - droppedLinebreak := false - j := 0 - for i, ch := range p.wsbuf { - switch ch { - case blank, vtab: - // ignore any horizontal whitespace before line breaks - p.wsbuf[i] = ignore - continue - case indent: - // apply pending indentation - continue - case unindent: - // if this is not the last unindent, apply it - // as it is (likely) belonging to the last - // construct (e.g., a multi-line expression list) - // and is not part of closing a block - if i+1 < len(p.wsbuf) && p.wsbuf[i+1] == unindent { - continue - } - // if the next token is not a closing }, apply the unindent - // if it appears that the comment is aligned with the - // token; otherwise assume the unindent is part of a - // closing block and stop (this scenario appears with - // comments before a case label where the comments - // apply to the next case instead of the current one) - if tok != token.RBRACE && pos.Column == next.Column { - continue - } - case newline, formfeed: - p.wsbuf[i] = ignore - droppedLinebreak = prev == nil // record only if first comment of a group - } - j = i - break - } - p.writeWhitespace(j) - - // determine number of linebreaks before the comment - n := 0 - if pos.IsValid() && p.last.IsValid() { - n = pos.Line - p.last.Line - if n < 0 { // should never happen - n = 0 - } - } - - // at the package scope level only (p.indent == 0), - // add an extra newline if we dropped one before: - // this preserves a blank line before documentation - // comments at the package scope level (issue 2570) - if p.indent == 0 && droppedLinebreak { - n++ - } - - // make sure there is at least one line break - // if the previous comment was a line comment - if n == 0 && prev != nil && prev.Text[1] == '/' { - n = 1 - } - - if n > 0 { - // use formfeeds to break columns before a comment; - // this is analogous to using formfeeds to separate - // individual lines of /*-style comments - p.writeByte('\f', nlimit(n)) - } - } -} - -// Returns true if s contains only white space -// (only tabs and blanks can appear in the printer's context). -func isBlank(s string) bool { - for i := 0; i < len(s); i++ { - if s[i] > ' ' { - return false - } - } - return true -} - -// commonPrefix returns the common prefix of a and b. -func commonPrefix(a, b string) string { - i := 0 - for i < len(a) && i < len(b) && a[i] == b[i] && (a[i] <= ' ' || a[i] == '*') { - i++ - } - return a[0:i] -} - -// trimRight returns s with trailing whitespace removed. -func trimRight(s string) string { - return strings.TrimRightFunc(s, unicode.IsSpace) -} - -// stripCommonPrefix removes a common prefix from /*-style comment lines (unless no -// comment line is indented, all but the first line have some form of space prefix). -// The prefix is computed using heuristics such that is likely that the comment -// contents are nicely laid out after re-printing each line using the printer's -// current indentation. -func stripCommonPrefix(lines []string) { - if len(lines) <= 1 { - return // at most one line - nothing to do - } - // len(lines) > 1 - - // The heuristic in this function tries to handle a few - // common patterns of /*-style comments: Comments where - // the opening /* and closing */ are aligned and the - // rest of the comment text is aligned and indented with - // blanks or tabs, cases with a vertical "line of stars" - // on the left, and cases where the closing */ is on the - // same line as the last comment text. - - // Compute maximum common white prefix of all but the first, - // last, and blank lines, and replace blank lines with empty - // lines (the first line starts with /* and has no prefix). - // In cases where only the first and last lines are not blank, - // such as two-line comments, or comments where all inner lines - // are blank, consider the last line for the prefix computation - // since otherwise the prefix would be empty. - // - // Note that the first and last line are never empty (they - // contain the opening /* and closing */ respectively) and - // thus they can be ignored by the blank line check. - prefix := "" - prefixSet := false - if len(lines) > 2 { - for i, line := range lines[1 : len(lines)-1] { - if isBlank(line) { - lines[1+i] = "" // range starts with lines[1] - } else { - if !prefixSet { - prefix = line - prefixSet = true - } - prefix = commonPrefix(prefix, line) - } - - } - } - // If we don't have a prefix yet, consider the last line. - if !prefixSet { - line := lines[len(lines)-1] - prefix = commonPrefix(line, line) - } - - /* - * Check for vertical "line of stars" and correct prefix accordingly. - */ - lineOfStars := false - if p, _, ok := stringsCut(prefix, "*"); ok { - // remove trailing blank from prefix so stars remain aligned - prefix = strings.TrimSuffix(p, " ") - lineOfStars = true - } else { - // No line of stars present. - // Determine the white space on the first line after the /* - // and before the beginning of the comment text, assume two - // blanks instead of the /* unless the first character after - // the /* is a tab. If the first comment line is empty but - // for the opening /*, assume up to 3 blanks or a tab. This - // whitespace may be found as suffix in the common prefix. - first := lines[0] - if isBlank(first[2:]) { - // no comment text on the first line: - // reduce prefix by up to 3 blanks or a tab - // if present - this keeps comment text indented - // relative to the /* and */'s if it was indented - // in the first place - i := len(prefix) - for n := 0; n < 3 && i > 0 && prefix[i-1] == ' '; n++ { - i-- - } - if i == len(prefix) && i > 0 && prefix[i-1] == '\t' { - i-- - } - prefix = prefix[0:i] - } else { - // comment text on the first line - suffix := make([]byte, len(first)) - n := 2 // start after opening /* - for n < len(first) && first[n] <= ' ' { - suffix[n] = first[n] - n++ - } - if n > 2 && suffix[2] == '\t' { - // assume the '\t' compensates for the /* - suffix = suffix[2:n] - } else { - // otherwise assume two blanks - suffix[0], suffix[1] = ' ', ' ' - suffix = suffix[0:n] - } - // Shorten the computed common prefix by the length of - // suffix, if it is found as suffix of the prefix. - prefix = strings.TrimSuffix(prefix, string(suffix)) - } - } - - // Handle last line: If it only contains a closing */, align it - // with the opening /*, otherwise align the text with the other - // lines. - last := lines[len(lines)-1] - closing := "*/" - before, _, _ := stringsCut(last, closing) // closing always present - if isBlank(before) { - // last line only contains closing */ - if lineOfStars { - closing = " */" // add blank to align final star - } - lines[len(lines)-1] = prefix + closing - } else { - // last line contains more comment text - assume - // it is aligned like the other lines and include - // in prefix computation - prefix = commonPrefix(prefix, last) - } - - // Remove the common prefix from all but the first and empty lines. - for i, line := range lines { - if i > 0 && line != "" { - lines[i] = line[len(prefix):] - } - } -} - -func (p *printer) writeComment(comment *ast.Comment) { - text := comment.Text - pos := p.posFor(comment.Pos()) - - const linePrefix = "//line " - if strings.HasPrefix(text, linePrefix) && (!pos.IsValid() || pos.Column == 1) { - // Possibly a //-style line directive. - // Suspend indentation temporarily to keep line directive valid. - defer func(indent int) { p.indent = indent }(p.indent) - p.indent = 0 - } - - // shortcut common case of //-style comments - if text[1] == '/' { - if constraint.IsGoBuild(text) { - p.goBuild = append(p.goBuild, len(p.output)) - } else if constraint.IsPlusBuild(text) { - p.plusBuild = append(p.plusBuild, len(p.output)) - } - p.writeString(pos, trimRight(text), true) - return - } - - // for /*-style comments, print line by line and let the - // write function take care of the proper indentation - lines := strings.Split(text, "\n") - - // The comment started in the first column but is going - // to be indented. For an idempotent result, add indentation - // to all lines such that they look like they were indented - // before - this will make sure the common prefix computation - // is the same independent of how many times formatting is - // applied (was issue 1835). - if pos.IsValid() && pos.Column == 1 && p.indent > 0 { - for i, line := range lines[1:] { - lines[1+i] = " " + line - } - } - - stripCommonPrefix(lines) - - // write comment lines, separated by formfeed, - // without a line break after the last line - for i, line := range lines { - if i > 0 { - p.writeByte('\f', 1) - pos = p.pos - } - if len(line) > 0 { - p.writeString(pos, trimRight(line), true) - } - } -} - -// writeCommentSuffix writes a line break after a comment if indicated -// and processes any leftover indentation information. If a line break -// is needed, the kind of break (newline vs formfeed) depends on the -// pending whitespace. The writeCommentSuffix result indicates if a -// newline was written or if a formfeed was dropped from the whitespace -// buffer. -func (p *printer) writeCommentSuffix(needsLinebreak bool) (wroteNewline, droppedFF bool) { - for i, ch := range p.wsbuf { - switch ch { - case blank, vtab: - // ignore trailing whitespace - p.wsbuf[i] = ignore - case indent, unindent: - // don't lose indentation information - case newline, formfeed: - // if we need a line break, keep exactly one - // but remember if we dropped any formfeeds - if needsLinebreak { - needsLinebreak = false - wroteNewline = true - } else { - if ch == formfeed { - droppedFF = true - } - p.wsbuf[i] = ignore - } - } - } - p.writeWhitespace(len(p.wsbuf)) - - // make sure we have a line break - if needsLinebreak { - p.writeByte('\n', 1) - wroteNewline = true - } - - return -} - -// containsLinebreak reports whether the whitespace buffer contains any line breaks. -func (p *printer) containsLinebreak() bool { - for _, ch := range p.wsbuf { - if ch == newline || ch == formfeed { - return true - } - } - return false -} - -// intersperseComments consumes all comments that appear before the next token -// tok and prints it together with the buffered whitespace (i.e., the whitespace -// that needs to be written before the next token). A heuristic is used to mix -// the comments and whitespace. The intersperseComments result indicates if a -// newline was written or if a formfeed was dropped from the whitespace buffer. -func (p *printer) intersperseComments(next token.Position, tok token.Token) (wroteNewline, droppedFF bool) { - var last *ast.Comment - for p.commentBefore(next) { - list := p.comment.List - changed := false - if p.lastTok != token.IMPORT && // do not rewrite cgo's import "C" comments - p.posFor(p.comment.Pos()).Column == 1 && - p.posFor(p.comment.End()+1) == next { - // Unindented comment abutting next token position: - // a top-level doc comment. - list = formatDocComment(list) - changed = true - - if len(p.comment.List) > 0 && len(list) == 0 { - // The doc comment was removed entirely. - // Keep preceding whitespace. - p.writeCommentPrefix(p.posFor(p.comment.Pos()), next, last, tok) - // Change print state to continue at next. - p.pos = next - p.last = next - // There can't be any more comments. - p.nextComment() - return p.writeCommentSuffix(false) - } - } - for _, c := range list { - p.writeCommentPrefix(p.posFor(c.Pos()), next, last, tok) - p.writeComment(c) - last = c - } - // In case list was rewritten, change print state to where - // the original list would have ended. - if len(p.comment.List) > 0 && changed { - last = p.comment.List[len(p.comment.List)-1] - p.pos = p.posFor(last.End()) - p.last = p.pos - } - p.nextComment() - } - - if last != nil { - // If the last comment is a /*-style comment and the next item - // follows on the same line but is not a comma, and not a "closing" - // token immediately following its corresponding "opening" token, - // add an extra separator unless explicitly disabled. Use a blank - // as separator unless we have pending linebreaks, they are not - // disabled, and we are outside a composite literal, in which case - // we want a linebreak (issue 15137). - // TODO(gri) This has become overly complicated. We should be able - // to track whether we're inside an expression or statement and - // use that information to decide more directly. - needsLinebreak := false - if p.mode&noExtraBlank == 0 && - last.Text[1] == '*' && p.lineFor(last.Pos()) == next.Line && - tok != token.COMMA && - (tok != token.RPAREN || p.prevOpen == token.LPAREN) && - (tok != token.RBRACK || p.prevOpen == token.LBRACK) { - if p.containsLinebreak() && p.mode&noExtraLinebreak == 0 && p.level == 0 { - needsLinebreak = true - } else { - p.writeByte(' ', 1) - } - } - // Ensure that there is a line break after a //-style comment, - // before EOF, and before a closing '}' unless explicitly disabled. - if last.Text[1] == '/' || - tok == token.EOF || - tok == token.RBRACE && p.mode&noExtraLinebreak == 0 { - needsLinebreak = true - } - return p.writeCommentSuffix(needsLinebreak) - } - - // no comment was written - we should never reach here since - // intersperseComments should not be called in that case - p.internalError("intersperseComments called without pending comments") - return -} - -// whiteWhitespace writes the first n whitespace entries. -func (p *printer) writeWhitespace(n int) { - // write entries - for i := 0; i < n; i++ { - switch ch := p.wsbuf[i]; ch { - case ignore: - // ignore! - case indent: - p.indent++ - case unindent: - p.indent-- - if p.indent < 0 { - p.internalError("negative indentation:", p.indent) - p.indent = 0 - } - case newline, formfeed: - // A line break immediately followed by a "correcting" - // unindent is swapped with the unindent - this permits - // proper label positioning. If a comment is between - // the line break and the label, the unindent is not - // part of the comment whitespace prefix and the comment - // will be positioned correctly indented. - if i+1 < n && p.wsbuf[i+1] == unindent { - // Use a formfeed to terminate the current section. - // Otherwise, a long label name on the next line leading - // to a wide column may increase the indentation column - // of lines before the label; effectively leading to wrong - // indentation. - p.wsbuf[i], p.wsbuf[i+1] = unindent, formfeed - i-- // do it again - continue - } - fallthrough - default: - p.writeByte(byte(ch), 1) - } - } - - // shift remaining entries down - l := copy(p.wsbuf, p.wsbuf[n:]) - p.wsbuf = p.wsbuf[:l] -} - -// ---------------------------------------------------------------------------- -// Printing interface - -// nlimit limits n to maxNewlines. -func nlimit(n int) int { - if n > maxNewlines { - n = maxNewlines - } - return n -} - -func mayCombine(prev token.Token, next byte) (b bool) { - switch prev { - case token.INT: - b = next == '.' // 1. - case token.ADD: - b = next == '+' // ++ - case token.SUB: - b = next == '-' // -- - case token.QUO: - b = next == '*' // /* - case token.LSS: - b = next == '-' || next == '<' // <- or << - case token.AND: - b = next == '&' || next == '^' // && or &^ - } - return -} - -// print prints a list of "items" (roughly corresponding to syntactic -// tokens, but also including whitespace and formatting information). -// It is the only print function that should be called directly from -// any of the AST printing functions in nodes.go. -// -// Whitespace is accumulated until a non-whitespace token appears. Any -// comments that need to appear before that token are printed first, -// taking into account the amount and structure of any pending white- -// space for best comment placement. Then, any leftover whitespace is -// printed, followed by the actual token. -func (p *printer) print(args ...interface{}) { - for _, arg := range args { - // information about the current arg - var data string - var isLit bool - var impliedSemi bool // value for p.impliedSemi after this arg - - // record previous opening token, if any - switch p.lastTok { - case token.ILLEGAL: - // ignore (white space) - case token.LPAREN, token.LBRACK: - p.prevOpen = p.lastTok - default: - // other tokens followed any opening token - p.prevOpen = token.ILLEGAL - } - - switch x := arg.(type) { - case pmode: - // toggle printer mode - p.mode ^= x - continue - - case whiteSpace: - if x == ignore { - // don't add ignore's to the buffer; they - // may screw up "correcting" unindents (see - // LabeledStmt) - continue - } - i := len(p.wsbuf) - if i == cap(p.wsbuf) { - // Whitespace sequences are very short so this should - // never happen. Handle gracefully (but possibly with - // bad comment placement) if it does happen. - p.writeWhitespace(i) - i = 0 - } - p.wsbuf = p.wsbuf[0 : i+1] - p.wsbuf[i] = x - if x == newline || x == formfeed { - // newlines affect the current state (p.impliedSemi) - // and not the state after printing arg (impliedSemi) - // because comments can be interspersed before the arg - // in this case - p.impliedSemi = false - } - p.lastTok = token.ILLEGAL - continue - - case *ast.Ident: - data = x.Name - impliedSemi = true - p.lastTok = token.IDENT - - case *ast.BasicLit: - data = x.Value - isLit = true - impliedSemi = true - p.lastTok = x.Kind - - case token.Token: - s := x.String() - if mayCombine(p.lastTok, s[0]) { - // the previous and the current token must be - // separated by a blank otherwise they combine - // into a different incorrect token sequence - // (except for token.INT followed by a '.' this - // should never happen because it is taken care - // of via binary expression formatting) - if len(p.wsbuf) != 0 { - p.internalError("whitespace buffer not empty") - } - p.wsbuf = p.wsbuf[0:1] - p.wsbuf[0] = ' ' - } - data = s - // some keywords followed by a newline imply a semicolon - switch x { - case token.BREAK, token.CONTINUE, token.FALLTHROUGH, token.RETURN, - token.INC, token.DEC, token.RPAREN, token.RBRACK, token.RBRACE: - impliedSemi = true - } - p.lastTok = x - - case token.Pos: - if x.IsValid() { - p.pos = p.posFor(x) // accurate position of next item - } - continue - - case string: - // incorrect AST - print error message - data = x - isLit = true - impliedSemi = true - p.lastTok = token.STRING - - default: - fmt.Fprintf(os.Stderr, "print: unsupported argument %v (%T)\n", arg, arg) - panic("go/printer type") - } - // data != "" - - next := p.pos // estimated/accurate position of next item - wroteNewline, droppedFF := p.flush(next, p.lastTok) - - // intersperse extra newlines if present in the source and - // if they don't cause extra semicolons (don't do this in - // flush as it will cause extra newlines at the end of a file) - if !p.impliedSemi { - n := nlimit(next.Line - p.pos.Line) - // don't exceed maxNewlines if we already wrote one - if wroteNewline && n == maxNewlines { - n = maxNewlines - 1 - } - if n > 0 { - ch := byte('\n') - if droppedFF { - ch = '\f' // use formfeed since we dropped one before - } - p.writeByte(ch, n) - impliedSemi = false - } - } - - // the next token starts now - record its line number if requested - if p.linePtr != nil { - *p.linePtr = p.out.Line - p.linePtr = nil - } - - p.writeString(next, data, isLit) - p.impliedSemi = impliedSemi - } -} - -// flush prints any pending comments and whitespace occurring textually -// before the position of the next token tok. The flush result indicates -// if a newline was written or if a formfeed was dropped from the whitespace -// buffer. -func (p *printer) flush(next token.Position, tok token.Token) (wroteNewline, droppedFF bool) { - if p.commentBefore(next) { - // if there are comments before the next item, intersperse them - wroteNewline, droppedFF = p.intersperseComments(next, tok) - } else { - // otherwise, write any leftover whitespace - p.writeWhitespace(len(p.wsbuf)) - } - return -} - -// getNode returns the ast.CommentGroup associated with n, if any. -func getDoc(n ast.Node) *ast.CommentGroup { - switch n := n.(type) { - case *ast.Field: - return n.Doc - case *ast.ImportSpec: - return n.Doc - case *ast.ValueSpec: - return n.Doc - case *ast.TypeSpec: - return n.Doc - case *ast.GenDecl: - return n.Doc - case *ast.FuncDecl: - return n.Doc - case *ast.File: - return n.Doc - } - return nil -} - -func getLastComment(n ast.Node) *ast.CommentGroup { - switch n := n.(type) { - case *ast.Field: - return n.Comment - case *ast.ImportSpec: - return n.Comment - case *ast.ValueSpec: - return n.Comment - case *ast.TypeSpec: - return n.Comment - case *ast.GenDecl: - if len(n.Specs) > 0 { - return getLastComment(n.Specs[len(n.Specs)-1]) - } - case *ast.File: - if len(n.Comments) > 0 { - return n.Comments[len(n.Comments)-1] - } - } - return nil -} - -func (p *printer) printNode(node interface{}) error { - // unpack *CommentedNode, if any - var comments []*ast.CommentGroup - if cnode, ok := node.(*CommentedNode); ok { - node = cnode.Node - comments = cnode.Comments - } - - if comments != nil { - // commented node - restrict comment list to relevant range - n, ok := node.(ast.Node) - if !ok { - goto unsupported - } - beg := n.Pos() - end := n.End() - // if the node has associated documentation, - // include that commentgroup in the range - // (the comment list is sorted in the order - // of the comment appearance in the source code) - if doc := getDoc(n); doc != nil { - beg = doc.Pos() - } - if com := getLastComment(n); com != nil { - if e := com.End(); e > end { - end = e - } - } - // token.Pos values are global offsets, we can - // compare them directly - i := 0 - for i < len(comments) && comments[i].End() < beg { - i++ - } - j := i - for j < len(comments) && comments[j].Pos() < end { - j++ - } - if i < j { - p.comments = comments[i:j] - } - } else if n, ok := node.(*ast.File); ok { - // use ast.File comments, if any - p.comments = n.Comments - } - - // if there are no comments, use node comments - p.useNodeComments = p.comments == nil - - // get comments ready for use - p.nextComment() - - p.print(pmode(0)) - - // format node - switch n := node.(type) { - case ast.Expr: - p.expr(n) - case ast.Stmt: - // A labeled statement will un-indent to position the label. - // Set p.indent to 1 so we don't get indent "underflow". - if _, ok := n.(*ast.LabeledStmt); ok { - p.indent = 1 - } - p.stmt(n, false) - case ast.Decl: - p.decl(n) - case ast.Spec: - p.spec(n, 1, false) - case []ast.Stmt: - // A labeled statement will un-indent to position the label. - // Set p.indent to 1 so we don't get indent "underflow". - for _, s := range n { - if _, ok := s.(*ast.LabeledStmt); ok { - p.indent = 1 - } - } - p.stmtList(n, 0, false) - case []ast.Decl: - p.declList(n) - case *ast.File: - p.file(n) - default: - goto unsupported - } - - return nil - -unsupported: - return fmt.Errorf("go/printer: unsupported node type %T", node) -} - -// ---------------------------------------------------------------------------- -// Trimmer - -// A trimmer is an io.Writer filter for stripping tabwriter.Escape -// characters, trailing blanks and tabs, and for converting formfeed -// and vtab characters into newlines and htabs (in case no tabwriter -// is used). Text bracketed by tabwriter.Escape characters is passed -// through unchanged. -type trimmer struct { - output io.Writer - state int - space []byte -} - -// trimmer is implemented as a state machine. -// It can be in one of the following states: -const ( - inSpace = iota // inside space - inEscape // inside text bracketed by tabwriter.Escapes - inText // inside text -) - -func (p *trimmer) resetSpace() { - p.state = inSpace - p.space = p.space[0:0] -} - -// Design note: It is tempting to eliminate extra blanks occurring in -// whitespace in this function as it could simplify some -// of the blanks logic in the node printing functions. -// However, this would mess up any formatting done by -// the tabwriter. - -var aNewline = []byte("\n") - -func (p *trimmer) Write(data []byte) (n int, err error) { - // invariants: - // p.state == inSpace: - // p.space is unwritten - // p.state == inEscape, inText: - // data[m:n] is unwritten - m := 0 - var b byte - for n, b = range data { - if b == '\v' { - b = '\t' // convert to htab - } - switch p.state { - case inSpace: - switch b { - case '\t', ' ': - p.space = append(p.space, b) - case '\n', '\f': - p.resetSpace() // discard trailing space - _, err = p.output.Write(aNewline) - case tabwriter.Escape: - _, err = p.output.Write(p.space) - p.state = inEscape - m = n + 1 // +1: skip tabwriter.Escape - default: - _, err = p.output.Write(p.space) - p.state = inText - m = n - } - case inEscape: - if b == tabwriter.Escape { - _, err = p.output.Write(data[m:n]) - p.resetSpace() - } - case inText: - switch b { - case '\t', ' ': - _, err = p.output.Write(data[m:n]) - p.resetSpace() - p.space = append(p.space, b) - case '\n', '\f': - _, err = p.output.Write(data[m:n]) - p.resetSpace() - if err == nil { - _, err = p.output.Write(aNewline) - } - case tabwriter.Escape: - _, err = p.output.Write(data[m:n]) - p.state = inEscape - m = n + 1 // +1: skip tabwriter.Escape - } - default: - panic("unreachable") - } - if err != nil { - return - } - } - n = len(data) - - switch p.state { - case inEscape, inText: - _, err = p.output.Write(data[m:n]) - p.resetSpace() - } - - return -} - -// ---------------------------------------------------------------------------- -// Public interface - -// A Mode value is a set of flags (or 0). They control printing. -type Mode uint - -const ( - RawFormat Mode = 1 << iota // do not use a tabwriter; if set, UseSpaces is ignored - TabIndent // use tabs for indentation independent of UseSpaces - UseSpaces // use spaces instead of tabs for alignment - SourcePos // emit //line directives to preserve original source positions -) - -// The mode below is not included in printer's public API because -// editing code text is deemed out of scope. Because this mode is -// unexported, it's also possible to modify or remove it based on -// the evolving needs of go/format and cmd/gofmt without breaking -// users. See discussion in CL 240683. -const ( - // normalizeNumbers means to canonicalize number - // literal prefixes and exponents while printing. - // - // This value is known in and used by go/format and cmd/gofmt. - // It is currently more convenient and performant for those - // packages to apply number normalization during printing, - // rather than by modifying the AST in advance. - normalizeNumbers Mode = 1 << 30 -) - -// A Config node controls the output of Fprint. -type Config struct { - Mode Mode // default: 0 - Tabwidth int // default: 8 - Indent int // default: 0 (all code is indented at least by this much) -} - -// fprint implements Fprint and takes a nodesSizes map for setting up the printer state. -func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node interface{}, nodeSizes map[ast.Node]int) (err error) { - // print node - var p printer - p.init(cfg, fset, nodeSizes) - if err = p.printNode(node); err != nil { - return - } - // print outstanding comments - p.impliedSemi = false // EOF acts like a newline - p.flush(token.Position{Offset: infinity, Line: infinity}, token.EOF) - - // output is buffered in p.output now. - // fix //go:build and // +build comments if needed. - p.fixGoBuildLines() - - // redirect output through a trimmer to eliminate trailing whitespace - // (Input to a tabwriter must be untrimmed since trailing tabs provide - // formatting information. The tabwriter could provide trimming - // functionality but no tabwriter is used when RawFormat is set.) - output = &trimmer{output: output} - - // redirect output through a tabwriter if necessary - if cfg.Mode&RawFormat == 0 { - minwidth := cfg.Tabwidth - - padchar := byte('\t') - if cfg.Mode&UseSpaces != 0 { - padchar = ' ' - } - - twmode := tabwriter.DiscardEmptyColumns - if cfg.Mode&TabIndent != 0 { - minwidth = 0 - twmode |= tabwriter.TabIndent - } - - output = tabwriter.NewWriter(output, minwidth, cfg.Tabwidth, 1, padchar, twmode) - } - - // write printer result via tabwriter/trimmer to output - if _, err = output.Write(p.output); err != nil { - return - } - - // flush tabwriter, if any - if tw, _ := output.(*tabwriter.Writer); tw != nil { - err = tw.Flush() - } - - return -} - -// A CommentedNode bundles an AST node and corresponding comments. -// It may be provided as argument to any of the Fprint functions. -type CommentedNode struct { - Node interface{} // *ast.File, or ast.Expr, ast.Decl, ast.Spec, or ast.Stmt - Comments []*ast.CommentGroup -} - -// Fprint "pretty-prints" an AST node to output for a given configuration cfg. -// Position information is interpreted relative to the file set fset. -// The node type must be *ast.File, *CommentedNode, []ast.Decl, []ast.Stmt, -// or assignment-compatible to ast.Expr, ast.Decl, ast.Spec, or ast.Stmt. -func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{}) error { - return cfg.fprint(output, fset, node, make(map[ast.Node]int)) -} - -// Fprint "pretty-prints" an AST node to output. -// It calls Config.Fprint with default settings. -// Note that gofmt uses tabs for indentation but spaces for alignment; -// use format.Node (package go/format) for output that matches gofmt. -func Fprint(output io.Writer, fset *token.FileSet, node interface{}) error { - return (&Config{Tabwidth: 8}).Fprint(output, fset, node) -} - -func stringsCut(s, sep string) (before, after string, found bool) { - if i := strings.Index(s, sep); i >= 0 { - return s[:i], s[i+len(sep):], true - } - return s, "", false -} diff --git a/internal/backport/go/printer/printer_test.go b/internal/backport/go/printer/printer_test.go deleted file mode 100644 index 9347d6b629..0000000000 --- a/internal/backport/go/printer/printer_test.go +++ /dev/null @@ -1,800 +0,0 @@ -// Copyright 2009 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 printer - -import ( - "bytes" - "errors" - "flag" - "fmt" - "io" - "os" - "path/filepath" - "testing" - "time" - - "golang.org/x/website/internal/backport/diff" - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/parser" - "golang.org/x/website/internal/backport/go/token" -) - -const ( - dataDir = "testdata" - tabwidth = 8 -) - -var update = flag.Bool("update", false, "update golden files") - -var fset = token.NewFileSet() - -type checkMode uint - -const ( - export checkMode = 1 << iota - rawFormat - normNumber - idempotent - allowTypeParams -) - -// format parses src, prints the corresponding AST, verifies the resulting -// src is syntactically correct, and returns the resulting src or an error -// if any. -func format(src []byte, mode checkMode) ([]byte, error) { - // parse src - f, err := parser.ParseFile(fset, "", src, parser.ParseComments) - if err != nil { - return nil, fmt.Errorf("parse: %s\n%s", err, src) - } - - // filter exports if necessary - if mode&export != 0 { - ast.FileExports(f) // ignore result - f.Comments = nil // don't print comments that are not in AST - } - - // determine printer configuration - cfg := Config{Tabwidth: tabwidth} - if mode&rawFormat != 0 { - cfg.Mode |= RawFormat - } - if mode&normNumber != 0 { - cfg.Mode |= normalizeNumbers - } - - // print AST - var buf bytes.Buffer - if err := cfg.Fprint(&buf, fset, f); err != nil { - return nil, fmt.Errorf("print: %s", err) - } - - // make sure formatted output is syntactically correct - res := buf.Bytes() - if _, err := parser.ParseFile(fset, "", res, parser.ParseComments); err != nil { - return nil, fmt.Errorf("re-parse: %s\n%s", err, buf.Bytes()) - } - - return res, nil -} - -// lineAt returns the line in text starting at offset offs. -func lineAt(text []byte, offs int) []byte { - i := offs - for i < len(text) && text[i] != '\n' { - i++ - } - return text[offs:i] -} - -// checkEqual compares a and b. -func checkEqual(aname, bname string, a, b []byte) error { - if bytes.Equal(a, b) { - return nil - } - return errors.New(string(diff.Diff(aname, a, bname, b))) -} - -func runcheck(t *testing.T, source, golden string, mode checkMode) { - src, err := os.ReadFile(source) - if err != nil { - t.Error(err) - return - } - - res, err := format(src, mode) - if err != nil { - t.Error(err) - return - } - - // update golden files if necessary - if *update { - if err := os.WriteFile(golden, res, 0644); err != nil { - t.Error(err) - } - return - } - - // get golden - gld, err := os.ReadFile(golden) - if err != nil { - t.Error(err) - return - } - - // formatted source and golden must be the same - if err := checkEqual(source, golden, res, gld); err != nil { - t.Error(err) - return - } - - if mode&idempotent != 0 { - // formatting golden must be idempotent - // (This is very difficult to achieve in general and for now - // it is only checked for files explicitly marked as such.) - res, err = format(gld, mode) - if err != nil { - t.Error(err) - return - } - if err := checkEqual(golden, fmt.Sprintf("format(%s)", golden), gld, res); err != nil { - t.Errorf("golden is not idempotent: %s", err) - } - } -} - -func check(t *testing.T, source, golden string, mode checkMode) { - // run the test - cc := make(chan int, 1) - go func() { - runcheck(t, source, golden, mode) - cc <- 0 - }() - - // wait with timeout - select { - case <-time.After(10 * time.Second): // plenty of a safety margin, even for very slow machines - // test running past time out - t.Errorf("%s: running too slowly", source) - case <-cc: - // test finished within allotted time margin - } -} - -type entry struct { - source, golden string - mode checkMode -} - -// Use go test -update to create/update the respective golden files. -var data = []entry{ - {"empty.input", "empty.golden", idempotent}, - {"comments.input", "comments.golden", 0}, - {"comments.input", "comments.x", export}, - {"comments2.input", "comments2.golden", idempotent}, - {"alignment.input", "alignment.golden", idempotent}, - {"linebreaks.input", "linebreaks.golden", idempotent}, - {"expressions.input", "expressions.golden", idempotent}, - {"expressions.input", "expressions.raw", rawFormat | idempotent}, - {"declarations.input", "declarations.golden", 0}, - {"statements.input", "statements.golden", 0}, - {"slow.input", "slow.golden", idempotent}, - {"complit.input", "complit.x", export}, - {"go2numbers.input", "go2numbers.golden", idempotent}, - {"go2numbers.input", "go2numbers.norm", normNumber | idempotent}, - {"generics.input", "generics.golden", idempotent | allowTypeParams}, - {"gobuild1.input", "gobuild1.golden", idempotent}, - {"gobuild2.input", "gobuild2.golden", idempotent}, - {"gobuild3.input", "gobuild3.golden", idempotent}, - {"gobuild4.input", "gobuild4.golden", idempotent}, - {"gobuild5.input", "gobuild5.golden", idempotent}, - {"gobuild6.input", "gobuild6.golden", idempotent}, - {"gobuild7.input", "gobuild7.golden", idempotent}, -} - -func TestFiles(t *testing.T) { - t.Parallel() - for _, e := range data { - source := filepath.Join(dataDir, e.source) - golden := filepath.Join(dataDir, e.golden) - mode := e.mode - t.Run(e.source, func(t *testing.T) { - t.Parallel() - check(t, source, golden, mode) - // TODO(gri) check that golden is idempotent - //check(t, golden, golden, e.mode) - }) - } -} - -// TestLineComments, using a simple test case, checks that consecutive line -// comments are properly terminated with a newline even if the AST position -// information is incorrect. -func TestLineComments(t *testing.T) { - const src = `// comment 1 - // comment 2 - // comment 3 - package main - ` - - fset := token.NewFileSet() - f, err := parser.ParseFile(fset, "", src, parser.ParseComments) - if err != nil { - panic(err) // error in test - } - - var buf bytes.Buffer - fset = token.NewFileSet() // use the wrong file set - Fprint(&buf, fset, f) - - nlines := 0 - for _, ch := range buf.Bytes() { - if ch == '\n' { - nlines++ - } - } - - const expected = 3 - if nlines < expected { - t.Errorf("got %d, expected %d\n", nlines, expected) - t.Errorf("result:\n%s", buf.Bytes()) - } -} - -// Verify that the printer can be invoked during initialization. -func init() { - const name = "foobar" - var buf bytes.Buffer - if err := Fprint(&buf, fset, &ast.Ident{Name: name}); err != nil { - panic(err) // error in test - } - // in debug mode, the result contains additional information; - // ignore it - if s := buf.String(); !debug && s != name { - panic("got " + s + ", want " + name) - } -} - -// Verify that the printer doesn't crash if the AST contains BadXXX nodes. -func TestBadNodes(t *testing.T) { - const src = "package p\n(" - const res = "package p\nBadDecl\n" - f, err := parser.ParseFile(fset, "", src, parser.ParseComments) - if err == nil { - t.Error("expected illegal program") // error in test - } - var buf bytes.Buffer - Fprint(&buf, fset, f) - if buf.String() != res { - t.Errorf("got %q, expected %q", buf.String(), res) - } -} - -// testComment verifies that f can be parsed again after printing it -// with its first comment set to comment at any possible source offset. -func testComment(t *testing.T, f *ast.File, srclen int, comment *ast.Comment) { - f.Comments[0].List[0] = comment - var buf bytes.Buffer - for offs := 0; offs <= srclen; offs++ { - buf.Reset() - // Printing f should result in a correct program no - // matter what the (incorrect) comment position is. - if err := Fprint(&buf, fset, f); err != nil { - t.Error(err) - } - if _, err := parser.ParseFile(fset, "", buf.Bytes(), 0); err != nil { - t.Fatalf("incorrect program for pos = %d:\n%s", comment.Slash, buf.String()) - } - // Position information is just an offset. - // Move comment one byte down in the source. - comment.Slash++ - } -} - -// Verify that the printer produces a correct program -// even if the position information of comments introducing newlines -// is incorrect. -func TestBadComments(t *testing.T) { - t.Parallel() - const src = ` -// first comment - text and position changed by test -package p -import "fmt" -const pi = 3.14 // rough circle -var ( - x, y, z int = 1, 2, 3 - u, v float64 -) -func fibo(n int) { - if n < 2 { - return n /* seed values */ - } - return fibo(n-1) + fibo(n-2) -} -` - - f, err := parser.ParseFile(fset, "", src, parser.ParseComments) - if err != nil { - t.Error(err) // error in test - } - - comment := f.Comments[0].List[0] - pos := comment.Pos() - if fset.PositionFor(pos, false /* absolute position */).Offset != 1 { - t.Error("expected offset 1") // error in test - } - - testComment(t, f, len(src), &ast.Comment{Slash: pos, Text: "//-style comment"}) - testComment(t, f, len(src), &ast.Comment{Slash: pos, Text: "/*-style comment */"}) - testComment(t, f, len(src), &ast.Comment{Slash: pos, Text: "/*-style \n comment */"}) - testComment(t, f, len(src), &ast.Comment{Slash: pos, Text: "/*-style comment \n\n\n */"}) -} - -type visitor chan *ast.Ident - -func (v visitor) Visit(n ast.Node) (w ast.Visitor) { - if ident, ok := n.(*ast.Ident); ok { - v <- ident - } - return v -} - -// idents is an iterator that returns all idents in f via the result channel. -func idents(f *ast.File) <-chan *ast.Ident { - v := make(visitor) - go func() { - ast.Walk(v, f) - close(v) - }() - return v -} - -// identCount returns the number of identifiers found in f. -func identCount(f *ast.File) int { - n := 0 - for range idents(f) { - n++ - } - return n -} - -// Verify that the SourcePos mode emits correct //line directives -// by testing that position information for matching identifiers -// is maintained. -func TestSourcePos(t *testing.T) { - const src = ` -package p -import ( "golang.org/x/website/internal/backport/go/printer"; "math" ) -const pi = 3.14; var x = 0 -type t struct{ x, y, z int; u, v, w float32 } -func (t *t) foo(a, b, c int) int { - return a*t.x + b*t.y + - // two extra lines here - // ... - c*t.z -} -` - - // parse original - f1, err := parser.ParseFile(fset, "src", src, parser.ParseComments) - if err != nil { - t.Fatal(err) - } - - // pretty-print original - var buf bytes.Buffer - err = (&Config{Mode: UseSpaces | SourcePos, Tabwidth: 8}).Fprint(&buf, fset, f1) - if err != nil { - t.Fatal(err) - } - - // parse pretty printed original - // (//line directives must be interpreted even w/o parser.ParseComments set) - f2, err := parser.ParseFile(fset, "", buf.Bytes(), 0) - if err != nil { - t.Fatalf("%s\n%s", err, buf.Bytes()) - } - - // At this point the position information of identifiers in f2 should - // match the position information of corresponding identifiers in f1. - - // number of identifiers must be > 0 (test should run) and must match - n1 := identCount(f1) - n2 := identCount(f2) - if n1 == 0 { - t.Fatal("got no idents") - } - if n2 != n1 { - t.Errorf("got %d idents; want %d", n2, n1) - } - - // verify that all identifiers have correct line information - i2range := idents(f2) - for i1 := range idents(f1) { - i2 := <-i2range - - if i2.Name != i1.Name { - t.Errorf("got ident %s; want %s", i2.Name, i1.Name) - } - - // here we care about the relative (line-directive adjusted) positions - l1 := fset.Position(i1.Pos()).Line - l2 := fset.Position(i2.Pos()).Line - if l2 != l1 { - t.Errorf("got line %d; want %d for %s", l2, l1, i1.Name) - } - } - - if t.Failed() { - t.Logf("\n%s", buf.Bytes()) - } -} - -// Verify that the SourcePos mode doesn't emit unnecessary //line directives -// before empty lines. -func TestIssue5945(t *testing.T) { - const orig = ` -package p // line 2 -func f() {} // line 3 - -var x, y, z int - - -func g() { // line 8 -} -` - - const want = `//line src.go:2 -package p - -//line src.go:3 -func f() {} - -var x, y, z int - -//line src.go:8 -func g() { -} -` - - // parse original - f1, err := parser.ParseFile(fset, "src.go", orig, 0) - if err != nil { - t.Fatal(err) - } - - // pretty-print original - var buf bytes.Buffer - err = (&Config{Mode: UseSpaces | SourcePos, Tabwidth: 8}).Fprint(&buf, fset, f1) - if err != nil { - t.Fatal(err) - } - got := buf.String() - - // compare original with desired output - if got != want { - t.Errorf("got:\n%s\nwant:\n%s\n", got, want) - } -} - -var decls = []string{ - `import "fmt"`, - "const pi = 3.1415\nconst e = 2.71828\n\nvar x = pi", - "func sum(x, y int) int\t{ return x + y }", -} - -func TestDeclLists(t *testing.T) { - for _, src := range decls { - file, err := parser.ParseFile(fset, "", "package p;"+src, parser.ParseComments) - if err != nil { - panic(err) // error in test - } - - var buf bytes.Buffer - err = Fprint(&buf, fset, file.Decls) // only print declarations - if err != nil { - panic(err) // error in test - } - - out := buf.String() - if out != src { - t.Errorf("\ngot : %q\nwant: %q\n", out, src) - } - } -} - -var stmts = []string{ - "i := 0", - "select {}\nvar a, b = 1, 2\nreturn a + b", - "go f()\ndefer func() {}()", -} - -func TestStmtLists(t *testing.T) { - for _, src := range stmts { - file, err := parser.ParseFile(fset, "", "package p; func _() {"+src+"}", parser.ParseComments) - if err != nil { - panic(err) // error in test - } - - var buf bytes.Buffer - err = Fprint(&buf, fset, file.Decls[0].(*ast.FuncDecl).Body.List) // only print statements - if err != nil { - panic(err) // error in test - } - - out := buf.String() - if out != src { - t.Errorf("\ngot : %q\nwant: %q\n", out, src) - } - } -} - -func TestBaseIndent(t *testing.T) { - t.Parallel() - // The testfile must not contain multi-line raw strings since those - // are not indented (because their values must not change) and make - // this test fail. - const filename = "printer.go" - src, err := os.ReadFile(filename) - if err != nil { - panic(err) // error in test - } - - file, err := parser.ParseFile(fset, filename, src, 0) - if err != nil { - panic(err) // error in test - } - - for indent := 0; indent < 4; indent++ { - indent := indent - t.Run(fmt.Sprint(indent), func(t *testing.T) { - t.Parallel() - var buf bytes.Buffer - (&Config{Tabwidth: tabwidth, Indent: indent}).Fprint(&buf, fset, file) - // all code must be indented by at least 'indent' tabs - lines := bytes.Split(buf.Bytes(), []byte{'\n'}) - for i, line := range lines { - if len(line) == 0 { - continue // empty lines don't have indentation - } - n := 0 - for j, b := range line { - if b != '\t' { - // end of indentation - n = j - break - } - } - if n < indent { - t.Errorf("line %d: got only %d tabs; want at least %d: %q", i, n, indent, line) - } - } - }) - } -} - -// TestFuncType tests that an ast.FuncType with a nil Params field -// can be printed (per go/ast specification). Test case for issue 3870. -func TestFuncType(t *testing.T) { - src := &ast.File{ - Name: &ast.Ident{Name: "p"}, - Decls: []ast.Decl{ - &ast.FuncDecl{ - Name: &ast.Ident{Name: "f"}, - Type: &ast.FuncType{}, - }, - }, - } - - var buf bytes.Buffer - if err := Fprint(&buf, fset, src); err != nil { - t.Fatal(err) - } - got := buf.String() - - const want = `package p - -func f() -` - - if got != want { - t.Fatalf("got:\n%s\nwant:\n%s\n", got, want) - } -} - -type limitWriter struct { - remaining int - errCount int -} - -func (l *limitWriter) Write(buf []byte) (n int, err error) { - n = len(buf) - if n >= l.remaining { - n = l.remaining - err = io.EOF - l.errCount++ - } - l.remaining -= n - return n, err -} - -// Test whether the printer stops writing after the first error -func TestWriteErrors(t *testing.T) { - t.Parallel() - const filename = "printer.go" - src, err := os.ReadFile(filename) - if err != nil { - panic(err) // error in test - } - file, err := parser.ParseFile(fset, filename, src, 0) - if err != nil { - panic(err) // error in test - } - for i := 0; i < 20; i++ { - lw := &limitWriter{remaining: i} - err := (&Config{Mode: RawFormat}).Fprint(lw, fset, file) - if lw.errCount > 1 { - t.Fatal("Writes continued after first error returned") - } - // We expect errCount be 1 iff err is set - if (lw.errCount != 0) != (err != nil) { - t.Fatal("Expected err when errCount != 0") - } - } -} - -// TextX is a skeleton test that can be filled in for debugging one-off cases. -// Do not remove. -func TestX(t *testing.T) { - const src = ` -package p -func _() {} -` - _, err := format([]byte(src), 0) - if err != nil { - t.Error(err) - } -} - -func TestCommentedNode(t *testing.T) { - const ( - input = `package main - -func foo() { - // comment inside func -} - -// leading comment -type bar int // comment2 - -` - - foo = `func foo() { - // comment inside func -}` - - bar = `// leading comment -type bar int // comment2 -` - ) - - fset := token.NewFileSet() - f, err := parser.ParseFile(fset, "input.go", input, parser.ParseComments) - if err != nil { - t.Fatal(err) - } - - var buf bytes.Buffer - - err = Fprint(&buf, fset, &CommentedNode{Node: f.Decls[0], Comments: f.Comments}) - if err != nil { - t.Fatal(err) - } - - if buf.String() != foo { - t.Errorf("got %q, want %q", buf.String(), foo) - } - - buf.Reset() - - err = Fprint(&buf, fset, &CommentedNode{Node: f.Decls[1], Comments: f.Comments}) - if err != nil { - t.Fatal(err) - } - - if buf.String() != bar { - t.Errorf("got %q, want %q", buf.String(), bar) - } -} - -func TestIssue11151(t *testing.T) { - const src = "package p\t/*\r/1\r*\r/2*\r\r\r\r/3*\r\r+\r\r/4*/\n" - fset := token.NewFileSet() - f, err := parser.ParseFile(fset, "", src, parser.ParseComments) - if err != nil { - t.Fatal(err) - } - - var buf bytes.Buffer - Fprint(&buf, fset, f) - got := buf.String() - const want = "package p\t/*/1*\r/2*\r/3*+/4*/\n" // \r following opening /* should be stripped - if got != want { - t.Errorf("\ngot : %q\nwant: %q", got, want) - } - - // the resulting program must be valid - _, err = parser.ParseFile(fset, "", got, 0) - if err != nil { - t.Errorf("%v\norig: %q\ngot : %q", err, src, got) - } -} - -// If a declaration has multiple specifications, a parenthesized -// declaration must be printed even if Lparen is token.NoPos. -func TestParenthesizedDecl(t *testing.T) { - // a package with multiple specs in a single declaration - const src = "package p; var ( a float64; b int )" - fset := token.NewFileSet() - f, err := parser.ParseFile(fset, "", src, 0) - if err != nil { - t.Fatal(err) - } - - // print the original package - var buf bytes.Buffer - err = Fprint(&buf, fset, f) - if err != nil { - t.Fatal(err) - } - original := buf.String() - - // now remove parentheses from the declaration - for i := 0; i != len(f.Decls); i++ { - f.Decls[i].(*ast.GenDecl).Lparen = token.NoPos - } - buf.Reset() - err = Fprint(&buf, fset, f) - if err != nil { - t.Fatal(err) - } - noparen := buf.String() - - if noparen != original { - t.Errorf("got %q, want %q", noparen, original) - } -} - -// Verify that we don't print a newline between "return" and its results, as -// that would incorrectly cause a naked return. -func TestIssue32854(t *testing.T) { - src := `package foo - -func f() { - return Composite{ - call(), - } -}` - fset := token.NewFileSet() - file, err := parser.ParseFile(fset, "", src, 0) - if err != nil { - panic(err) - } - - // Replace the result with call(), which is on the next line. - fd := file.Decls[0].(*ast.FuncDecl) - ret := fd.Body.List[0].(*ast.ReturnStmt) - ret.Results[0] = ret.Results[0].(*ast.CompositeLit).Elts[0] - - var buf bytes.Buffer - if err := Fprint(&buf, fset, ret); err != nil { - t.Fatal(err) - } - want := "return call()" - if got := buf.String(); got != want { - t.Fatalf("got %q, want %q", got, want) - } -} diff --git a/internal/backport/go/printer/testdata/alignment.golden b/internal/backport/go/printer/testdata/alignment.golden deleted file mode 100644 index 96086ed906..0000000000 --- a/internal/backport/go/printer/testdata/alignment.golden +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright 2018 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 alignment - -// ---------------------------------------------------------------------------- -// Examples from issue #7335. - -func main() { - z := MyStruct{ - Foo: "foo", - Bar: "bar", - Name: "name", - LongName: "longname", - Baz: "baz", - } - y := MyStruct{ - Foo: "foo", - Bar: "bar", - NameXX: "name", - LongNameXXXXXXXXXXXXX: "longname", - Baz: "baz", - } - z := MyStruct{ - Foo: "foo", - Bar: "bar", - Name: "name", - LongNameXXXXXXXXXXXXX: "longname", - Baz: "baz", - } -} - -// ---------------------------------------------------------------------------- -// Examples from issue #10392. - -var kcfg = KubeletConfig{ - Address: s.Address, - AllowPrivileged: s.AllowPrivileged, - HostNetworkSources: hostNetworkSources, - HostnameOverride: s.HostnameOverride, - RootDirectory: s.RootDirectory, - ConfigFile: s.Config, - ManifestURL: s.ManifestURL, - FileCheckFrequency: s.FileCheckFrequency, - HTTPCheckFrequency: s.HTTPCheckFrequency, - PodInfraContainerImage: s.PodInfraContainerImage, - SyncFrequency: s.SyncFrequency, - RegistryPullQPS: s.RegistryPullQPS, - RegistryBurst: s.RegistryBurst, - MinimumGCAge: s.MinimumGCAge, - MaxPerPodContainerCount: s.MaxPerPodContainerCount, - MaxContainerCount: s.MaxContainerCount, - ClusterDomain: s.ClusterDomain, - ClusterDNS: s.ClusterDNS, - Runonce: s.RunOnce, - Port: s.Port, - ReadOnlyPort: s.ReadOnlyPort, - CadvisorInterface: cadvisorInterface, - EnableServer: s.EnableServer, - EnableDebuggingHandlers: s.EnableDebuggingHandlers, - DockerClient: dockertools.ConnectToDockerOrDie(s.DockerEndpoint), - KubeClient: client, - MasterServiceNamespace: s.MasterServiceNamespace, - VolumePlugins: ProbeVolumePlugins(), - NetworkPlugins: ProbeNetworkPlugins(), - NetworkPluginName: s.NetworkPluginName, - StreamingConnectionIdleTimeout: s.StreamingConnectionIdleTimeout, - TLSOptions: tlsOptions, - ImageGCPolicy: imageGCPolicy, imageGCPolicy, - Cloud: cloud, - NodeStatusUpdateFrequency: s.NodeStatusUpdateFrequency, -} - -var a = A{ - Long: 1, - LongLong: 1, - LongLongLong: 1, - LongLongLongLong: 1, - LongLongLongLongLong: 1, - LongLongLongLongLongLong: 1, - LongLongLongLongLongLongLong: 1, - LongLongLongLongLongLongLongLong: 1, - Short: 1, - LongLongLongLongLongLongLongLongLong: 3, -} - -// ---------------------------------------------------------------------------- -// Examples from issue #22852. - -var fmtMap = map[string]string{ - "1": "123", - "12": "123", - "123": "123", - "1234": "123", - "12345": "123", - "123456": "123", - "12345678901234567890123456789": "123", - "abcde": "123", - "123456789012345678901234567890": "123", - "1234567": "123", - "abcdefghijklmnopqrstuvwxyzabcd": "123", - "abcd": "123", -} - -type Fmt struct { - abcdefghijklmnopqrstuvwx string - abcdefghijklmnopqrstuvwxy string - abcdefghijklmnopqrstuvwxyz string - abcdefghijklmnopqrstuvwxyza string - abcdefghijklmnopqrstuvwxyzab string - abcdefghijklmnopqrstuvwxyzabc string - abcde string - abcdefghijklmnopqrstuvwxyzabcde string - abcdefg string -} - -func main() { - _ := Fmt{ - abcdefghijklmnopqrstuvwx: "foo", - abcdefghijklmnopqrstuvwxyza: "foo", - abcdefghijklmnopqrstuvwxyzab: "foo", - abcdefghijklmnopqrstuvwxyzabc: "foo", - abcde: "foo", - abcdefghijklmnopqrstuvwxyzabcde: "foo", - abcdefg: "foo", - abcdefghijklmnopqrstuvwxy: "foo", - abcdefghijklmnopqrstuvwxyz: "foo", - } -} - -// ---------------------------------------------------------------------------- -// Examples from issue #26352. - -var _ = map[int]string{ - 1: "", - - 12345678901234567890123456789: "", - 12345678901234567890123456789012345678: "", -} - -func f() { - _ = map[int]string{ - 1: "", - - 12345678901234567: "", - 12345678901234567890123456789012345678901: "", - } -} - -// ---------------------------------------------------------------------------- -// Examples from issue #26930. - -var _ = S{ - F1: []string{}, - F2____: []string{}, -} - -var _ = S{ - F1: []string{}, - F2____: []string{}, -} - -var _ = S{ - F1____: []string{}, - F2: []string{}, -} - -var _ = S{ - F1____: []string{}, - F2: []string{}, -} diff --git a/internal/backport/go/printer/testdata/alignment.input b/internal/backport/go/printer/testdata/alignment.input deleted file mode 100644 index 323d2689e0..0000000000 --- a/internal/backport/go/printer/testdata/alignment.input +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright 2018 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 alignment - -// ---------------------------------------------------------------------------- -// Examples from issue #7335. - -func main() { - z := MyStruct{ - Foo: "foo", - Bar: "bar", - Name: "name", - LongName: "longname", - Baz: "baz", - } - y := MyStruct{ - Foo: "foo", - Bar: "bar", - NameXX: "name", - LongNameXXXXXXXXXXXXX: "longname", - Baz: "baz", - } - z := MyStruct{ - Foo: "foo", - Bar: "bar", - Name: "name", - LongNameXXXXXXXXXXXXX: "longname", - Baz: "baz", - } -} - -// ---------------------------------------------------------------------------- -// Examples from issue #10392. - -var kcfg = KubeletConfig{ - Address: s.Address, - AllowPrivileged: s.AllowPrivileged, - HostNetworkSources: hostNetworkSources, - HostnameOverride: s.HostnameOverride, - RootDirectory: s.RootDirectory, - ConfigFile: s.Config, - ManifestURL: s.ManifestURL, - FileCheckFrequency: s.FileCheckFrequency, - HTTPCheckFrequency: s.HTTPCheckFrequency, - PodInfraContainerImage: s.PodInfraContainerImage, - SyncFrequency: s.SyncFrequency, - RegistryPullQPS: s.RegistryPullQPS, - RegistryBurst: s.RegistryBurst, - MinimumGCAge: s.MinimumGCAge, - MaxPerPodContainerCount: s.MaxPerPodContainerCount, - MaxContainerCount: s.MaxContainerCount, - ClusterDomain: s.ClusterDomain, - ClusterDNS: s.ClusterDNS, - Runonce: s.RunOnce, - Port: s.Port, - ReadOnlyPort: s.ReadOnlyPort, - CadvisorInterface: cadvisorInterface, - EnableServer: s.EnableServer, - EnableDebuggingHandlers: s.EnableDebuggingHandlers, - DockerClient: dockertools.ConnectToDockerOrDie(s.DockerEndpoint), - KubeClient: client, - MasterServiceNamespace: s.MasterServiceNamespace, - VolumePlugins: ProbeVolumePlugins(), - NetworkPlugins: ProbeNetworkPlugins(), - NetworkPluginName: s.NetworkPluginName, - StreamingConnectionIdleTimeout: s.StreamingConnectionIdleTimeout, - TLSOptions: tlsOptions, - ImageGCPolicy: imageGCPolicy,imageGCPolicy, - Cloud: cloud, - NodeStatusUpdateFrequency: s.NodeStatusUpdateFrequency, -} - -var a = A{ - Long: 1, - LongLong: 1, - LongLongLong: 1, - LongLongLongLong: 1, - LongLongLongLongLong: 1, - LongLongLongLongLongLong: 1, - LongLongLongLongLongLongLong: 1, - LongLongLongLongLongLongLongLong: 1, - Short: 1, - LongLongLongLongLongLongLongLongLong: 3, -} - -// ---------------------------------------------------------------------------- -// Examples from issue #22852. - -var fmtMap = map[string]string{ - "1": "123", - "12": "123", - "123": "123", - "1234": "123", - "12345": "123", - "123456": "123", - "12345678901234567890123456789": "123", - "abcde": "123", - "123456789012345678901234567890": "123", - "1234567": "123", - "abcdefghijklmnopqrstuvwxyzabcd": "123", - "abcd": "123", -} - -type Fmt struct { - abcdefghijklmnopqrstuvwx string - abcdefghijklmnopqrstuvwxy string - abcdefghijklmnopqrstuvwxyz string - abcdefghijklmnopqrstuvwxyza string - abcdefghijklmnopqrstuvwxyzab string - abcdefghijklmnopqrstuvwxyzabc string - abcde string - abcdefghijklmnopqrstuvwxyzabcde string - abcdefg string -} - -func main() { - _ := Fmt{ - abcdefghijklmnopqrstuvwx: "foo", - abcdefghijklmnopqrstuvwxyza: "foo", - abcdefghijklmnopqrstuvwxyzab: "foo", - abcdefghijklmnopqrstuvwxyzabc: "foo", - abcde: "foo", - abcdefghijklmnopqrstuvwxyzabcde: "foo", - abcdefg: "foo", - abcdefghijklmnopqrstuvwxy: "foo", - abcdefghijklmnopqrstuvwxyz: "foo", - } -} - -// ---------------------------------------------------------------------------- -// Examples from issue #26352. - -var _ = map[int]string{ - 1: "", - - 12345678901234567890123456789: "", - 12345678901234567890123456789012345678: "", -} - -func f() { - _ = map[int]string{ - 1: "", - - 12345678901234567: "", - 12345678901234567890123456789012345678901: "", - } -} - -// ---------------------------------------------------------------------------- -// Examples from issue #26930. - -var _ = S{ - F1: []string{ - }, - F2____: []string{}, -} - -var _ = S{ - F1: []string{ - - - }, - F2____: []string{}, -} - -var _ = S{ - F1____: []string{ - }, - F2: []string{}, -} - -var _ = S{ - F1____: []string{ - - }, - F2: []string{}, -} diff --git a/internal/backport/go/printer/testdata/comments.golden b/internal/backport/go/printer/testdata/comments.golden deleted file mode 100644 index 1e5d17b4e1..0000000000 --- a/internal/backport/go/printer/testdata/comments.golden +++ /dev/null @@ -1,774 +0,0 @@ -// Copyright 2009 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. - -// This is a package for testing comment placement by go/printer. -package main - -import "fmt" // fmt - -const c0 = 0 // zero -const ( - c1 = iota // c1 - c2 // c2 -) - -// Alignment of comments in declarations> -const ( - _ T = iota // comment - _ // comment - _ // comment - _ = iota + 10 - _ // comments - - _ = 10 // comment - _ T = 20 // comment -) - -const ( - _____ = iota // foo - _ // bar - _ = 0 // bal - _ // bat -) - -const ( - _ T = iota // comment - _ // comment - _ // comment - _ = iota + 10 - _ // comment - _ = 10 - _ = 20 // comment - _ T = 0 // comment -) - -// The SZ struct; it is empty. -type SZ struct{} - -// The S0 struct; no field is exported. -type S0 struct { - int - x, y, z int // 3 unexported fields -} - -// The S1 struct; some fields are not exported. -type S1 struct { - S0 - A, B, C float // 3 exported fields - D, b, c int // 2 unexported fields -} - -// The S2 struct; all fields are exported. -type S2 struct { - S1 - A, B, C float // 3 exported fields -} - -// The IZ interface; it is empty. -type SZ interface{} - -// The I0 interface; no method is exported. -type I0 interface { - f(x int) int // unexported method -} - -// The I1 interface; some methods are not exported. -type I1 interface { - I0 - F(x float) float // exported methods - g(x int) int // unexported method -} - -// The I2 interface; all methods are exported. -type I2 interface { - I0 - F(x float) float // exported method - G(x float) float // exported method -} - -// The S3 struct; all comments except for the last one must appear in the export. -type S3 struct { - // lead comment for F1 - F1 int // line comment for F1 - // lead comment for F2 - F2 int // line comment for F2 - f3 int // f3 is not exported -} - -// Here is a comment. -// Here is an accidentally unindented line. -// More comment. -// -//dir:ect ive -type directiveCheck struct{} - -// This comment group should be separated -// with a newline from the next comment -// group. - -// This comment should NOT be associated with the next declaration. - -var x int // x -var () - -// This comment SHOULD be associated with f0. -func f0() { - const pi = 3.14 // pi - var s1 struct{} /* an empty struct */ /* foo */ - // a struct constructor - // -------------------- - var s2 struct{} = struct{}{} - x := pi -} - -// This comment should be associated with f1, with one blank line before the comment. -func f1() { - f0() - /* 1 */ - // 2 - /* 3 */ - /* 4 */ - f0() -} - -func _() { - // this comment should be properly indented -} - -func _(x int) int { - if x < 0 { // the tab printed before this comment's // must not affect the remaining lines - return -x // this statement should be properly indented - } - if x < 0 { /* the tab printed before this comment's /* must not affect the remaining lines */ - return -x // this statement should be properly indented - } - return x -} - -func typeswitch(x interface{}) { - switch v := x.(type) { - case bool, int, float: - case string: - default: - } - - switch x.(type) { - } - - switch v0, ok := x.(int); v := x.(type) { - } - - switch v0, ok := x.(int); x.(type) { - case byte: // this comment should be on the same line as the keyword - // this comment should be normally indented - _ = 0 - case bool, int, float: - // this comment should be indented - case string: - default: - // this comment should be indented - } - // this comment should not be indented -} - -// -// Indentation of comments after possibly indented multi-line constructs -// (test cases for issue 3147). -// - -func _() { - s := 1 + - 2 - // should be indented like s -} - -func _() { - s := 1 + - 2 // comment - // should be indented like s -} - -func _() { - s := 1 + - 2 // comment - // should be indented like s - _ = 0 -} - -func _() { - s := 1 + - 2 - // should be indented like s - _ = 0 -} - -func _() { - s := 1 + - 2 - - // should be indented like s -} - -func _() { - s := 1 + - 2 // comment - - // should be indented like s -} - -func _() { - s := 1 + - 2 // comment - - // should be indented like s - _ = 0 -} - -func _() { - s := 1 + - 2 - - // should be indented like s - _ = 0 -} - -// Test case from issue 3147. -func f() { - templateText := "a" + // A - "b" + // B - "c" // C - - // should be aligned with f() - f() -} - -// Modified test case from issue 3147. -func f() { - templateText := "a" + // A - "b" + // B - "c" // C - - // may not be aligned with f() (source is not aligned) - f() -} - -// -// Test cases for alignment of lines in general comments. -// - -func _() { - /* freestanding comment - aligned line - aligned line - */ -} - -func _() { - /* freestanding comment - aligned line - aligned line - */ -} - -func _() { - /* freestanding comment - aligned line - aligned line */ -} - -func _() { - /* freestanding comment - aligned line - aligned line - */ -} - -func _() { - /* freestanding comment - aligned line - aligned line - */ -} - -func _() { - /* freestanding comment - aligned line - aligned line */ -} - -func _() { - /* - freestanding comment - aligned line - aligned line - */ -} - -func _() { - /* - freestanding comment - aligned line - aligned line - */ -} - -func _() { - /* - freestanding comment - aligned line - aligned line */ -} - -func _() { - /* - freestanding comment - aligned line - aligned line - */ -} - -func _() { - /* - freestanding comment - aligned line - aligned line - */ -} - -func _() { - /* - freestanding comment - aligned line - aligned line */ -} - -func _() { - /* freestanding comment - aligned line - */ -} - -func _() { - /* freestanding comment - aligned line - */ -} - -func _() { - /* freestanding comment - aligned line */ -} - -func _() { - /* freestanding comment - aligned line - */ -} - -func _() { - /* freestanding comment - aligned line - */ -} - -func _() { - /* freestanding comment - aligned line */ -} - -func _() { - /* - freestanding comment - aligned line - */ -} - -func _() { - /* - freestanding comment - aligned line - */ -} - -func _() { - /* - freestanding comment - aligned line */ -} - -func _() { - /* - freestanding comment - aligned line - */ -} - -func _() { - /* - freestanding comment - aligned line - */ -} - -func _() { - /* - freestanding comment - aligned line */ -} - -// Issue 9751. -func _() { - /*a string - - b string*/ - - /*A string - - - - Z string*/ - - /*a string - - b string - - c string*/ - - { - /*a string - b string*/ - - /*a string - - b string*/ - - /*a string - - b string - - c string*/ - } - - { - /*a string - b string*/ - - /*a string - - b string*/ - - /*a string - - b string - - c string*/ - } - - /* - */ - - /* - - */ - - /* - - * line - - */ -} - -/* - * line - * of - * stars - */ - -/* another line - * of - * stars */ - -/* and another line - * of - * stars */ - -/* a line of - * stars */ - -/* and another line of - * stars */ - -/* a line of stars - */ - -/* and another line of - */ - -/* a line of stars - */ - -/* and another line of - */ - -/* -aligned in middle -here - not here -*/ - -/* -blank line in middle: - -with no leading spaces on blank line. -*/ - -/* - aligned in middle - here - not here -*/ - -/* - blank line in middle: - - with no leading spaces on blank line. -*/ - -func _() { - /* - * line - * of - * stars - */ - - /* - aligned in middle - here - not here - */ - - /* - blank line in middle: - - with no leading spaces on blank line. - */ -} - -// Some interesting interspersed comments. -// See below for more common cases. -func _( /* this */ x /* is */ /* an */ int) { -} - -func _( /* no params - extra blank before and after comment */ ) {} -func _(a, b int /* params - no extra blank after comment */) {} - -func _() { f( /* no args - extra blank before and after comment */ ) } -func _() { f(a, b /* args - no extra blank after comment */) } - -func _() { - f( /* no args - extra blank before and after comment */ ) - f(a, b /* args - no extra blank after comment */) -} - -func ( /* comment1 */ T /* comment2 */) _() {} - -func _() { /* "short-ish one-line functions with comments are formatted as multi-line functions */ } -func _() { x := 0; /* comment */ y = x /* comment */ } - -func _() { - _ = 0 - /* closing curly brace should be on new line */ -} - -func _() { - _ = []int{0, 1 /* don't introduce a newline after this comment - was issue 1365 */} -} - -// Test cases from issue 1542: -// Comments must not be placed before commas and cause invalid programs. -func _() { - var a = []int{1, 2 /*jasldf*/} - _ = a -} - -func _() { - var a = []int{1, 2}/*jasldf - */ - - _ = a -} - -func _() { - var a = []int{1, 2}// jasldf - - _ = a -} - -// Test cases from issues 11274, 15137: -// Semicolon must not be lost when multiple statements are on the same line with a comment. -func _() { - x := 0 /**/ - y := 1 -} - -func _() { - f() - f() - f() /* comment */ - f() - f() /* comment */ - f() - f() /* a */ /* b */ - f() - f() /* a */ /* b */ - f() - f() /* a */ /* b */ - f() -} - -func _() { - f() /* a */ /* b */ -} - -// Comments immediately adjacent to punctuation followed by a newline -// remain after the punctuation (looks better and permits alignment of -// comments). -func _() { - _ = T{ - 1, // comment after comma - 2, /* comment after comma */ - 3, // comment after comma - } - _ = T{ - 1, // comment after comma - 2, /* comment after comma */ - 3, // comment after comma - } - _ = T{ - /* comment before literal */ 1, - 2, /* comment before comma - ok to move after comma */ - 3, /* comment before comma - ok to move after comma */ - } - - for i = 0; // comment after semicolon - i < 9; /* comment after semicolon */ - i++ { // comment after opening curly brace - } - - // TODO(gri) the last comment in this example should be aligned */ - for i = 0; // comment after semicolon - i < 9; /* comment before semicolon - ok to move after semicolon */ - i++ /* comment before opening curly brace */ { - } -} - -// If there is no newline following punctuation, commas move before the punctuation. -// This way, commas interspersed in lists stay with the respective expression. -func f(x /* comment */, y int, z int /* comment */, u, v, w int /* comment */) { - f(x /* comment */, y) - f(x, /* comment */ - y) - f( - x, /* comment */ - ) -} - -func g( - x int, /* comment */ -) { -} - -type _ struct { - a, b /* comment */, c int -} - -type _ struct { - a, b /* comment */, c int -} - -func _() { - for a /* comment */, b := range x { - } -} - -//extern foo -func foo() {} - -//export bar -func bar() {} - -// Print line directives correctly. - -// The following is a legal line directive. -// -//line foo:1 -func _() { - _ = 0 - // The following is a legal line directive. It must not be indented: -//line foo:2 - _ = 1 - - // The following is not a legal line directive (it doesn't start in column 1): - //line foo:2 - _ = 2 - - // The following is not a legal line directive (missing colon): -//line foo -3 - _ = 3 -} - -// Line comments with tabs -func _() { - var finput *bufio.Reader // input file - var stderr *bufio.Writer - var ftable *bufio.Writer // y.go file - var foutput *bufio.Writer // y.output file - - var oflag string // -o [y.go] - y.go file - var vflag string // -v [y.output] - y.output file - var lflag bool // -l - disable line directives -} - -// Trailing white space in comments should be trimmed -func _() { - // This comment has 4 blanks following that should be trimmed: - /* Each line of this comment has blanks or tabs following that should be trimmed: - line 2: - line 3: - */ -} - -var _ = []T{ /* lone comment */ } - -var _ = []T{ - /* lone comment */ -} - -var _ = []T{ - // lone comments - // in composite lit -} - -var _ = [][]T{ - { - // lone comments - // in composite lit - }, -} - -// TODO: gofmt doesn't add these tabs; make it so that these golden -// tests run the printer in a way that it's exactly like gofmt. - -var _ = []T{ // lone comment -} - -var _ = []T{ // lone comments - // in composite lit -} - -func _() {} - -func _() {} - -/* This comment is the last entry in this file. It must be printed and should be followed by a newline */ diff --git a/internal/backport/go/printer/testdata/comments.input b/internal/backport/go/printer/testdata/comments.input deleted file mode 100644 index 40aa55be06..0000000000 --- a/internal/backport/go/printer/testdata/comments.input +++ /dev/null @@ -1,773 +0,0 @@ -// Copyright 2009 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. - -// This is a package for testing comment placement by go/printer. -// -package main - -import "fmt" // fmt - -const c0 = 0 // zero -const ( - c1 = iota // c1 - c2 // c2 -) - -// Alignment of comments in declarations> -const ( - _ T = iota // comment - _ // comment - _ // comment - _ = iota+10 - _ // comments - - _ = 10 // comment - _ T = 20 // comment -) - -const ( - _____ = iota // foo - _ // bar - _ = 0 // bal - _ // bat -) - -const ( - _ T = iota // comment - _ // comment - _ // comment - _ = iota + 10 - _ // comment - _ = 10 - _ = 20 // comment - _ T = 0 // comment -) - -// The SZ struct; it is empty. -type SZ struct {} - -// The S0 struct; no field is exported. -type S0 struct { - int - x, y, z int // 3 unexported fields -} - -// The S1 struct; some fields are not exported. -type S1 struct { - S0 - A, B, C float // 3 exported fields - D, b, c int // 2 unexported fields -} - -// The S2 struct; all fields are exported. -type S2 struct { - S1 - A, B, C float // 3 exported fields -} - -// The IZ interface; it is empty. -type SZ interface {} - -// The I0 interface; no method is exported. -type I0 interface { - f(x int) int // unexported method -} - -// The I1 interface; some methods are not exported. -type I1 interface { - I0 - F(x float) float // exported methods - g(x int) int // unexported method -} - -// The I2 interface; all methods are exported. -type I2 interface { - I0 - F(x float) float // exported method - G(x float) float // exported method -} - -// The S3 struct; all comments except for the last one must appear in the export. -type S3 struct { - // lead comment for F1 - F1 int // line comment for F1 - // lead comment for F2 - F2 int // line comment for F2 - f3 int // f3 is not exported -} - -// Here is a comment. -//Here is an accidentally unindented line. -//dir:ect ive -// More comment. -type directiveCheck struct{} - -// This comment group should be separated -// with a newline from the next comment -// group. - -// This comment should NOT be associated with the next declaration. - -var x int // x -var () - - -// This comment SHOULD be associated with f0. -func f0() { - const pi = 3.14 // pi - var s1 struct {} /* an empty struct */ /* foo */ - // a struct constructor - // -------------------- - var s2 struct {} = struct {}{} - x := pi -} -// -// This comment should be associated with f1, with one blank line before the comment. -// -func f1() { - f0() - /* 1 */ - // 2 - /* 3 */ - /* 4 */ - f0() -} - - -func _() { -// this comment should be properly indented -} - - -func _(x int) int { - if x < 0 { // the tab printed before this comment's // must not affect the remaining lines - return -x // this statement should be properly indented - } - if x < 0 { /* the tab printed before this comment's /* must not affect the remaining lines */ - return -x // this statement should be properly indented - } - return x -} - - -func typeswitch(x interface{}) { - switch v := x.(type) { - case bool, int, float: - case string: - default: - } - - switch x.(type) { - } - - switch v0, ok := x.(int); v := x.(type) { - } - - switch v0, ok := x.(int); x.(type) { - case byte: // this comment should be on the same line as the keyword - // this comment should be normally indented - _ = 0 - case bool, int, float: - // this comment should be indented - case string: - default: - // this comment should be indented - } - // this comment should not be indented -} - -// -// Indentation of comments after possibly indented multi-line constructs -// (test cases for issue 3147). -// - -func _() { - s := 1 + - 2 -// should be indented like s -} - -func _() { - s := 1 + - 2 // comment - // should be indented like s -} - -func _() { - s := 1 + - 2 // comment - // should be indented like s - _ = 0 -} - -func _() { - s := 1 + - 2 - // should be indented like s - _ = 0 -} - -func _() { - s := 1 + - 2 - -// should be indented like s -} - -func _() { - s := 1 + - 2 // comment - - // should be indented like s -} - -func _() { - s := 1 + - 2 // comment - - // should be indented like s - _ = 0 -} - -func _() { - s := 1 + - 2 - - // should be indented like s - _ = 0 -} - -// Test case from issue 3147. -func f() { - templateText := "a" + // A - "b" + // B - "c" // C - - // should be aligned with f() - f() -} - -// Modified test case from issue 3147. -func f() { - templateText := "a" + // A - "b" + // B - "c" // C - - // may not be aligned with f() (source is not aligned) - f() -} - -// -// Test cases for alignment of lines in general comments. -// - -func _() { - /* freestanding comment - aligned line - aligned line - */ -} - -func _() { - /* freestanding comment - aligned line - aligned line - */ -} - -func _() { - /* freestanding comment - aligned line - aligned line */ -} - -func _() { - /* freestanding comment - aligned line - aligned line - */ -} - -func _() { - /* freestanding comment - aligned line - aligned line - */ -} - -func _() { - /* freestanding comment - aligned line - aligned line */ -} - - -func _() { - /* - freestanding comment - aligned line - aligned line - */ -} - -func _() { - /* - freestanding comment - aligned line - aligned line - */ -} - -func _() { - /* - freestanding comment - aligned line - aligned line */ -} - -func _() { - /* - freestanding comment - aligned line - aligned line - */ -} - -func _() { - /* - freestanding comment - aligned line - aligned line - */ -} - -func _() { - /* - freestanding comment - aligned line - aligned line */ -} - -func _() { - /* freestanding comment - aligned line - */ -} - -func _() { - /* freestanding comment - aligned line - */ -} - -func _() { - /* freestanding comment - aligned line */ -} - -func _() { - /* freestanding comment - aligned line - */ -} - -func _() { - /* freestanding comment - aligned line - */ -} - -func _() { - /* freestanding comment - aligned line */ -} - - -func _() { - /* - freestanding comment - aligned line - */ -} - -func _() { - /* - freestanding comment - aligned line - */ -} - -func _() { - /* - freestanding comment - aligned line */ -} - -func _() { - /* - freestanding comment - aligned line - */ -} - -func _() { - /* - freestanding comment - aligned line - */ -} - -func _() { - /* - freestanding comment - aligned line */ -} - -// Issue 9751. -func _() { - /*a string - - b string*/ - - /*A string - - - - Z string*/ - - /*a string - - b string - - c string*/ - - { - /*a string -b string*/ - - /*a string - -b string*/ - - /*a string - -b string - -c string*/ - } - - { - /*a string - b string*/ - - /*a string - - b string*/ - - /*a string - - b string - - c string*/ - } - - /* - */ - - /* - - */ - - /* - - * line - - */ -} - -/* - * line - * of - * stars - */ - -/* another line - * of - * stars */ - -/* and another line - * of - * stars */ - -/* a line of - * stars */ - -/* and another line of - * stars */ - -/* a line of stars -*/ - -/* and another line of -*/ - -/* a line of stars - */ - -/* and another line of - */ - -/* -aligned in middle -here - not here -*/ - -/* -blank line in middle: - -with no leading spaces on blank line. -*/ - -/* - aligned in middle - here - not here -*/ - -/* - blank line in middle: - - with no leading spaces on blank line. -*/ - -func _() { - /* - * line - * of - * stars - */ - - /* - aligned in middle - here - not here - */ - - /* - blank line in middle: - - with no leading spaces on blank line. -*/ -} - - -// Some interesting interspersed comments. -// See below for more common cases. -func _(/* this */x/* is *//* an */ int) { -} - -func _(/* no params - extra blank before and after comment */) {} -func _(a, b int /* params - no extra blank after comment */) {} - -func _() { f(/* no args - extra blank before and after comment */) } -func _() { f(a, b /* args - no extra blank after comment */) } - -func _() { - f(/* no args - extra blank before and after comment */) - f(a, b /* args - no extra blank after comment */) -} - -func (/* comment1 */ T /* comment2 */) _() {} - -func _() { /* "short-ish one-line functions with comments are formatted as multi-line functions */ } -func _() { x := 0; /* comment */ y = x /* comment */ } - -func _() { - _ = 0 - /* closing curly brace should be on new line */ } - -func _() { - _ = []int{0, 1 /* don't introduce a newline after this comment - was issue 1365 */} -} - -// Test cases from issue 1542: -// Comments must not be placed before commas and cause invalid programs. -func _() { - var a = []int{1, 2, /*jasldf*/ - } - _ = a -} - -func _() { - var a = []int{1, 2, /*jasldf - */ - } - _ = a -} - -func _() { - var a = []int{1, 2, // jasldf - } - _ = a -} - -// Test cases from issues 11274, 15137: -// Semicolon must not be lost when multiple statements are on the same line with a comment. -func _() { - x := 0 /**/; y := 1 -} - -func _() { - f(); f() - f(); /* comment */ f() - f() /* comment */; f() - f(); /* a */ /* b */ f() - f() /* a */ /* b */; f() - f() /* a */; /* b */ f() -} - -func _() { - f() /* a */ /* b */ } - -// Comments immediately adjacent to punctuation followed by a newline -// remain after the punctuation (looks better and permits alignment of -// comments). -func _() { - _ = T{ - 1, // comment after comma - 2, /* comment after comma */ - 3 , // comment after comma - } - _ = T{ - 1 ,// comment after comma - 2 ,/* comment after comma */ - 3,// comment after comma - } - _ = T{ - /* comment before literal */1, - 2/* comment before comma - ok to move after comma */, - 3 /* comment before comma - ok to move after comma */ , - } - - for - i=0;// comment after semicolon - i<9;/* comment after semicolon */ - i++{// comment after opening curly brace - } - - // TODO(gri) the last comment in this example should be aligned */ - for - i=0;// comment after semicolon - i<9/* comment before semicolon - ok to move after semicolon */; - i++ /* comment before opening curly brace */ { - } -} - -// If there is no newline following punctuation, commas move before the punctuation. -// This way, commas interspersed in lists stay with the respective expression. -func f(x/* comment */, y int, z int /* comment */, u, v, w int /* comment */) { - f(x /* comment */, y) - f(x /* comment */, - y) - f( - x /* comment */, - ) -} - -func g( - x int /* comment */, -) {} - -type _ struct { - a, b /* comment */, c int -} - -type _ struct { a, b /* comment */, c int } - -func _() { - for a /* comment */, b := range x { - } -} - -//extern foo -func foo() {} - -//export bar -func bar() {} - -// Print line directives correctly. - -// The following is a legal line directive. -//line foo:1 -func _() { - _ = 0 -// The following is a legal line directive. It must not be indented: -//line foo:2 - _ = 1 - -// The following is not a legal line directive (it doesn't start in column 1): - //line foo:2 - _ = 2 - -// The following is not a legal line directive (missing colon): -//line foo -3 - _ = 3 -} - -// Line comments with tabs -func _() { -var finput *bufio.Reader // input file -var stderr *bufio.Writer -var ftable *bufio.Writer // y.go file -var foutput *bufio.Writer // y.output file - -var oflag string // -o [y.go] - y.go file -var vflag string // -v [y.output] - y.output file -var lflag bool // -l - disable line directives -} - -// Trailing white space in comments should be trimmed -func _() { -// This comment has 4 blanks following that should be trimmed: -/* Each line of this comment has blanks or tabs following that should be trimmed: - line 2: - line 3: -*/ -} - -var _ = []T{/* lone comment */} - -var _ = []T{ -/* lone comment */ -} - -var _ = []T{ -// lone comments -// in composite lit -} - -var _ = [][]T{ - { - // lone comments - // in composite lit - }, -} - -// TODO: gofmt doesn't add these tabs; make it so that these golden -// tests run the printer in a way that it's exactly like gofmt. - -var _ = []T{// lone comment -} - -var _ = []T{// lone comments -// in composite lit -} - -func _() {} - -// -func _() {} - -/* This comment is the last entry in this file. It must be printed and should be followed by a newline */ diff --git a/internal/backport/go/printer/testdata/comments.x b/internal/backport/go/printer/testdata/comments.x deleted file mode 100644 index 5d088ab2c3..0000000000 --- a/internal/backport/go/printer/testdata/comments.x +++ /dev/null @@ -1,55 +0,0 @@ -// This is a package for testing comment placement by go/printer. -package main - -// The SZ struct; it is empty. -type SZ struct{} - -// The S0 struct; no field is exported. -type S0 struct { - // contains filtered or unexported fields -} - -// The S1 struct; some fields are not exported. -type S1 struct { - S0 - A, B, C float // 3 exported fields - D int // 2 unexported fields - // contains filtered or unexported fields -} - -// The S2 struct; all fields are exported. -type S2 struct { - S1 - A, B, C float // 3 exported fields -} - -// The IZ interface; it is empty. -type SZ interface{} - -// The I0 interface; no method is exported. -type I0 interface { - // contains filtered or unexported methods -} - -// The I1 interface; some methods are not exported. -type I1 interface { - I0 - F(x float) float // exported methods - // contains filtered or unexported methods -} - -// The I2 interface; all methods are exported. -type I2 interface { - I0 - F(x float) float // exported method - G(x float) float // exported method -} - -// The S3 struct; all comments except for the last one must appear in the export. -type S3 struct { - // lead comment for F1 - F1 int // line comment for F1 - // lead comment for F2 - F2 int // line comment for F2 - // contains filtered or unexported fields -} diff --git a/internal/backport/go/printer/testdata/comments2.golden b/internal/backport/go/printer/testdata/comments2.golden deleted file mode 100644 index 83213d1a9d..0000000000 --- a/internal/backport/go/printer/testdata/comments2.golden +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright 2012 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. - -// This is a package for testing comment placement by go/printer. -package main - -// Test cases for idempotent comment formatting (was issue 1835). -/* -c1a -*/ -/* - c1b -*/ -/* foo -c1c -*/ -/* foo - c1d -*/ -/* -c1e -foo */ -/* - c1f - foo */ - -func f() { - /* - c2a - */ - /* - c2b - */ - /* foo - c2c - */ - /* foo - c2d - */ - /* - c2e - foo */ - /* - c2f - foo */ -} - -func g() { - /* - c3a - */ - /* - c3b - */ - /* foo - c3c - */ - /* foo - c3d - */ - /* - c3e - foo */ - /* - c3f - foo */ -} - -// Test case taken literally from issue 1835. -func main() { - /* - prints test 5 times - */ - for i := 0; i < 5; i++ { - println("test") - } -} - -func issue5623() { -L: - _ = yyyyyyyyyyyyyyyy // comment - should be aligned - _ = xxxxxxxxxxxxxxxxxxxxxxxxxxxx /* comment */ - - _ = yyyyyyyyyyyyyyyy /* comment - should be aligned */ - _ = xxxxxxxxxxxxxxxxxxxxxxxxxxxx // comment - -LLLLLLL: - _ = yyyyyyyyyyyyyyyy // comment - should be aligned - _ = xxxxxxxxxxxxxxxxxxxxxxxxxxxx // comment - -LL: -LLLLL: - _ = xxxxxxxxxxxxxxxxxxxxxxxxxxxx /* comment */ - _ = yyyyyyyyyyyyyyyy /* comment - should be aligned */ - - _ = xxxxxxxxxxxxxxxxxxxxxxxxxxxx // comment - _ = yyyyyyyyyyyyyyyy // comment - should be aligned - - // test case from issue -label: - mask := uint64(1)<>4 - _ = "foo" + s - _ = s + "foo" - _ = 'a' + 'b' - _ = len(s) / 2 - _ = len(t0.x) / a - - // spaces around expressions of different precedence or expressions containing spaces - _ = a + -b - _ = a - ^b - _ = a / *p - _ = a + b*c - _ = 1 + b*c - _ = a + 2*c - _ = a + c*2 - _ = 1 + 2*3 - _ = s[1 : 2*3] - _ = s[a : b-c] - _ = s[0:] - _ = s[a+b] - _ = s[:b-c] - _ = s[a+b:] - _ = a[a< b - _ = a >= b - _ = a < b - _ = a <= b - _ = a < b && c > d - _ = a < b || c > d - - // spaces around "long" operands - _ = a + longIdentifier1 - _ = longIdentifier1 + a - _ = longIdentifier1 + longIdentifier2*longIdentifier3 - _ = s + "a longer string" - - // some selected cases - _ = a + t0.x - _ = a + t0.x + t1.x*t2.x - _ = a + b + c + d + e + 2*3 - _ = a + b + c + 2*3 + d + e - _ = (a + b + c) * 2 - _ = a - b + c - d + (a + b + c) + d&e - _ = under_bar - 1 - _ = Open(dpath+"/file", O_WRONLY|O_CREAT, 0666) - _ = int(c0&_Mask4)<<18 | int(c1&_Maskx)<<12 | int(c2&_Maskx)<<6 | int(c3&_Maskx) - - // test case for issue 8021 - // want: - // ([]bool{})[([]int{})[((1)+(((1)+((((1)*(((1)+(1))+(1)))+(1))*(1)))+(1)))]] - _ = ([]bool{})[([]int{})[((1)+(((1)+((((1)*(((1)+(1))+(1)))+(1))*(1)))+(1)))]] - - // the parser does not restrict expressions that may appear as statements - true - 42 - "foo" - x - (x) - a + b - a + b + c - a + (b * c) - a + (b / c) - 1 + a - a + 1 - s[a] - x << 1 - (s[0] << 1) & 0xf - "foo" + s - x == y - x < y || z > 42 -} - -// slice expressions with cap -func _() { - _ = x[a:b:c] - _ = x[a : b : c+d] - _ = x[a : b+d : c] - _ = x[a : b+d : c+d] - _ = x[a+d : b : c] - _ = x[a+d : b : c+d] - _ = x[a+d : b+d : c] - _ = x[a+d : b+d : c+d] - - _ = x[:b:c] - _ = x[: b : c+d] - _ = x[: b+d : c] - _ = x[: b+d : c+d] -} - -func issue22111() { - _ = x[:] - - _ = x[:b] - _ = x[:b+1] - - _ = x[a:] - _ = x[a+1:] - - _ = x[a:b] - _ = x[a+1 : b] - _ = x[a : b+1] - _ = x[a+1 : b+1] - - _ = x[:b:c] - _ = x[: b+1 : c] - _ = x[: b : c+1] - _ = x[: b+1 : c+1] - - _ = x[a:b:c] - _ = x[a+1 : b : c] - _ = x[a : b+1 : c] - _ = x[a+1 : b+1 : c] - _ = x[a : b : c+1] - _ = x[a+1 : b : c+1] - _ = x[a : b+1 : c+1] - _ = x[a+1 : b+1 : c+1] -} - -func _() { - _ = a + b - _ = a + b + c - _ = a + b*c - _ = a + (b * c) - _ = (a + b) * c - _ = a + (b * c * d) - _ = a + (b*c + d) - - _ = 1 << x - _ = -1 << x - _ = 1<>4 - - b.buf = b.buf[0 : b.off+m+n] - b.buf = b.buf[0 : b.off+m*n] - f(b.buf[0 : b.off+m+n]) - - signed += ' ' * 8 - tw.octal(header[148:155], chksum) - - _ = x > 0 && i >= 0 - - x1, x0 := x>>w2, x&m2 - z0 = t1<>w2) >> w2 - q1, r1 := x1/d1, x1%d1 - r1 = r1*b2 | x0>>w2 - x1 = (x1 << z) | (x0 >> (uint(w) - z)) - x1 = x1<>(uint(w)-z) - - _ = buf[0 : len(buf)+1] - _ = buf[0 : n+1] - - a, b = b, a - a = b + c - a = b*c + d - _ = a*b + c - _ = a - b - c - _ = a - (b - c) - _ = a - b*c - _ = a - (b * c) - _ = a * b / c - _ = a / *b - _ = x[a|^b] - _ = x[a / *b] - _ = a & ^b - _ = a + +b - _ = a - -b - _ = x[a*-b] - _ = x[a + +b] - _ = x ^ y ^ z - _ = b[a>>24] ^ b[(a>>16)&0xFF] ^ b[(a>>8)&0xFF] ^ b[a&0xFF] - _ = len(longVariableName) * 2 - - _ = token(matchType + xlength<> 4 - _ = "foo"+s - _ = s+"foo" - _ = 'a'+'b' - _ = len(s)/2 - _ = len(t0.x)/a - - // spaces around expressions of different precedence or expressions containing spaces - _ = a + -b - _ = a - ^b - _ = a / *p - _ = a + b*c - _ = 1 + b*c - _ = a + 2*c - _ = a + c*2 - _ = 1 + 2*3 - _ = s[1 : 2*3] - _ = s[a : b-c] - _ = s[0:] - _ = s[a+b] - _ = s[: b-c] - _ = s[a+b :] - _ = a[a< b - _ = a >= b - _ = a < b - _ = a <= b - _ = a < b && c > d - _ = a < b || c > d - - // spaces around "long" operands - _ = a + longIdentifier1 - _ = longIdentifier1 + a - _ = longIdentifier1 + longIdentifier2 * longIdentifier3 - _ = s + "a longer string" - - // some selected cases - _ = a + t0.x - _ = a + t0.x + t1.x * t2.x - _ = a + b + c + d + e + 2*3 - _ = a + b + c + 2*3 + d + e - _ = (a+b+c)*2 - _ = a - b + c - d + (a+b+c) + d&e - _ = under_bar-1 - _ = Open(dpath + "/file", O_WRONLY | O_CREAT, 0666) - _ = int(c0&_Mask4)<<18 | int(c1&_Maskx)<<12 | int(c2&_Maskx)<<6 | int(c3&_Maskx) - - // test case for issue 8021 - // want: - // ([]bool{})[([]int{})[((1)+(((1)+((((1)*(((1)+(1))+(1)))+(1))*(1)))+(1)))]] - _ = ([]bool{})[([]int{})[((1) + (((((1) + (((((((1) * (((((1) + (1))) + (1))))) + (1))) * (1))))) + (1))))]] - - // the parser does not restrict expressions that may appear as statements - true - 42 - "foo" - x - (x) - a+b - a+b+c - a+(b*c) - a+(b/c) - 1+a - a+1 - s[a] - x<<1 - (s[0]<<1)&0xf - "foo"+s - x == y - x < y || z > 42 -} - - -// slice expressions with cap -func _() { - _ = x[a:b:c] - _ = x[a:b:c+d] - _ = x[a:b+d:c] - _ = x[a:b+d:c+d] - _ = x[a+d:b:c] - _ = x[a+d:b:c+d] - _ = x[a+d:b+d:c] - _ = x[a+d:b+d:c+d] - - _ = x[:b:c] - _ = x[:b:c+d] - _ = x[:b+d:c] - _ = x[:b+d:c+d] -} - -func issue22111() { - _ = x[:] - - _ = x[:b] - _ = x[:b+1] - - _ = x[a:] - _ = x[a+1:] - - _ = x[a:b] - _ = x[a+1:b] - _ = x[a:b+1] - _ = x[a+1:b+1] - - _ = x[:b:c] - _ = x[:b+1:c] - _ = x[:b:c+1] - _ = x[:b+1:c+1] - - _ = x[a:b:c] - _ = x[a+1:b:c] - _ = x[a:b+1:c] - _ = x[a+1:b+1:c] - _ = x[a:b:c+1] - _ = x[a+1:b:c+1] - _ = x[a:b+1:c+1] - _ = x[a+1:b+1:c+1] -} - -func _() { - _ = a+b - _ = a+b+c - _ = a+b*c - _ = a+(b*c) - _ = (a+b)*c - _ = a+(b*c*d) - _ = a+(b*c+d) - - _ = 1<>4 - - b.buf = b.buf[0:b.off+m+n] - b.buf = b.buf[0:b.off+m*n] - f(b.buf[0:b.off+m+n]) - - signed += ' '*8 - tw.octal(header[148:155], chksum) - - _ = x > 0 && i >= 0 - - x1, x0 := x>>w2, x&m2 - z0 = t1<>w2)>>w2 - q1, r1 := x1/d1, x1%d1 - r1 = r1*b2 | x0>>w2 - x1 = (x1<>(uint(w)-z)) - x1 = x1<>(uint(w)-z) - - _ = buf[0:len(buf)+1] - _ = buf[0:n+1] - - a,b = b,a - a = b+c - a = b*c+d - _ = a*b+c - _ = a-b-c - _ = a-(b-c) - _ = a-b*c - _ = a-(b*c) - _ = a*b/c - _ = a/ *b - _ = x[a|^b] - _ = x[a/ *b] - _ = a& ^b - _ = a+ +b - _ = a- -b - _ = x[a*-b] - _ = x[a+ +b] - _ = x^y^z - _ = b[a>>24] ^ b[(a>>16)&0xFF] ^ b[(a>>8)&0xFF] ^ b[a&0xFF] - _ = len(longVariableName)*2 - - _ = token(matchType + xlength<>4 - _ = "foo" + s - _ = s + "foo" - _ = 'a' + 'b' - _ = len(s) / 2 - _ = len(t0.x) / a - - // spaces around expressions of different precedence or expressions containing spaces - _ = a + -b - _ = a - ^b - _ = a / *p - _ = a + b*c - _ = 1 + b*c - _ = a + 2*c - _ = a + c*2 - _ = 1 + 2*3 - _ = s[1 : 2*3] - _ = s[a : b-c] - _ = s[0:] - _ = s[a+b] - _ = s[:b-c] - _ = s[a+b:] - _ = a[a< b - _ = a >= b - _ = a < b - _ = a <= b - _ = a < b && c > d - _ = a < b || c > d - - // spaces around "long" operands - _ = a + longIdentifier1 - _ = longIdentifier1 + a - _ = longIdentifier1 + longIdentifier2*longIdentifier3 - _ = s + "a longer string" - - // some selected cases - _ = a + t0.x - _ = a + t0.x + t1.x*t2.x - _ = a + b + c + d + e + 2*3 - _ = a + b + c + 2*3 + d + e - _ = (a + b + c) * 2 - _ = a - b + c - d + (a + b + c) + d&e - _ = under_bar - 1 - _ = Open(dpath+"/file", O_WRONLY|O_CREAT, 0666) - _ = int(c0&_Mask4)<<18 | int(c1&_Maskx)<<12 | int(c2&_Maskx)<<6 | int(c3&_Maskx) - - // test case for issue 8021 - // want: - // ([]bool{})[([]int{})[((1)+(((1)+((((1)*(((1)+(1))+(1)))+(1))*(1)))+(1)))]] - _ = ([]bool{})[([]int{})[((1)+(((1)+((((1)*(((1)+(1))+(1)))+(1))*(1)))+(1)))]] - - // the parser does not restrict expressions that may appear as statements - true - 42 - "foo" - x - (x) - a + b - a + b + c - a + (b * c) - a + (b / c) - 1 + a - a + 1 - s[a] - x << 1 - (s[0] << 1) & 0xf - "foo" + s - x == y - x < y || z > 42 -} - -// slice expressions with cap -func _() { - _ = x[a:b:c] - _ = x[a : b : c+d] - _ = x[a : b+d : c] - _ = x[a : b+d : c+d] - _ = x[a+d : b : c] - _ = x[a+d : b : c+d] - _ = x[a+d : b+d : c] - _ = x[a+d : b+d : c+d] - - _ = x[:b:c] - _ = x[: b : c+d] - _ = x[: b+d : c] - _ = x[: b+d : c+d] -} - -func issue22111() { - _ = x[:] - - _ = x[:b] - _ = x[:b+1] - - _ = x[a:] - _ = x[a+1:] - - _ = x[a:b] - _ = x[a+1 : b] - _ = x[a : b+1] - _ = x[a+1 : b+1] - - _ = x[:b:c] - _ = x[: b+1 : c] - _ = x[: b : c+1] - _ = x[: b+1 : c+1] - - _ = x[a:b:c] - _ = x[a+1 : b : c] - _ = x[a : b+1 : c] - _ = x[a+1 : b+1 : c] - _ = x[a : b : c+1] - _ = x[a+1 : b : c+1] - _ = x[a : b+1 : c+1] - _ = x[a+1 : b+1 : c+1] -} - -func _() { - _ = a + b - _ = a + b + c - _ = a + b*c - _ = a + (b * c) - _ = (a + b) * c - _ = a + (b * c * d) - _ = a + (b*c + d) - - _ = 1 << x - _ = -1 << x - _ = 1<>4 - - b.buf = b.buf[0 : b.off+m+n] - b.buf = b.buf[0 : b.off+m*n] - f(b.buf[0 : b.off+m+n]) - - signed += ' ' * 8 - tw.octal(header[148:155], chksum) - - _ = x > 0 && i >= 0 - - x1, x0 := x>>w2, x&m2 - z0 = t1<>w2) >> w2 - q1, r1 := x1/d1, x1%d1 - r1 = r1*b2 | x0>>w2 - x1 = (x1 << z) | (x0 >> (uint(w) - z)) - x1 = x1<>(uint(w)-z) - - _ = buf[0 : len(buf)+1] - _ = buf[0 : n+1] - - a, b = b, a - a = b + c - a = b*c + d - _ = a*b + c - _ = a - b - c - _ = a - (b - c) - _ = a - b*c - _ = a - (b * c) - _ = a * b / c - _ = a / *b - _ = x[a|^b] - _ = x[a / *b] - _ = a & ^b - _ = a + +b - _ = a - -b - _ = x[a*-b] - _ = x[a + +b] - _ = x ^ y ^ z - _ = b[a>>24] ^ b[(a>>16)&0xFF] ^ b[(a>>8)&0xFF] ^ b[a&0xFF] - _ = len(longVariableName) * 2 - - _ = token(matchType + xlength< /tmp/16gig.txt - // tar -b 1 -c -f- /tmp/16gig.txt | dd bs=512 count=8 > writer-big.tar - &writerTest{ - file: "testdata/writer-big.tar", - entries: []*writerTestEntry{ - &writerTestEntry{ - header: &Header{ - Name: "tmp/16gig.txt", - Mode: 0640, - Uid: 73025, - Gid: 5000, - Size: 16 << 30, - Mtime: 1254699560, - Typeflag: '0', - Uname: "dsymonds", - Gname: "eng", - }, - // no contents - }, - }, - }, -} - -type untarTest struct { - file string - headers []*Header -} - -var untarTests = []*untarTest{ - &untarTest{ - file: "testdata/gnu.tar", - headers: []*Header{ - &Header{ - Name: "small.txt", - Mode: 0640, - Uid: 73025, - Gid: 5000, - Size: 5, - Mtime: 1244428340, - Typeflag: '0', - Uname: "dsymonds", - Gname: "eng", - }, - &Header{ - Name: "small2.txt", - Mode: 0640, - Uid: 73025, - Gid: 5000, - Size: 11, - Mtime: 1244436044, - Typeflag: '0', - Uname: "dsymonds", - Gname: "eng", - }, - }, - }, - &untarTest{ - file: "testdata/star.tar", - headers: []*Header{ - &Header{ - Name: "small.txt", - Mode: 0640, - Uid: 73025, - Gid: 5000, - Size: 5, - Mtime: 1244592783, - Typeflag: '0', - Uname: "dsymonds", - Gname: "eng", - Atime: 1244592783, - Ctime: 1244592783, - }, - &Header{ - Name: "small2.txt", - Mode: 0640, - Uid: 73025, - Gid: 5000, - Size: 11, - Mtime: 1244592783, - Typeflag: '0', - Uname: "dsymonds", - Gname: "eng", - Atime: 1244592783, - Ctime: 1244592783, - }, - }, - }, - &untarTest{ - file: "testdata/v7.tar", - headers: []*Header{ - &Header{ - Name: "small.txt", - Mode: 0444, - Uid: 73025, - Gid: 5000, - Size: 5, - Mtime: 1244593104, - Typeflag: '\x00', - }, - &Header{ - Name: "small2.txt", - Mode: 0444, - Uid: 73025, - Gid: 5000, - Size: 11, - Mtime: 1244593104, - Typeflag: '\x00', - }, - }, - }, -} - -var facts = map[int]string{ - 0: "1", - 1: "1", - 2: "2", - 10: "3628800", - 20: "2432902008176640000", - 100: "933262154439441526816992388562667004907159682643816214685929" + - "638952175999932299156089414639761565182862536979208272237582" + - "51185210916864000000000000000000000000", -} - -func usage() { - fmt.Fprintf(os.Stderr, - // TODO(gri): the 2nd string of this string list should not be indented - "usage: godoc package [name ...]\n"+ - " godoc -http=:6060\n") - flag.PrintDefaults() - os.Exit(2) -} - -func TestReader(t *testing.T) { -testLoop: - for i, test := range untarTests { - f, err := os.Open(test.file, os.O_RDONLY, 0444) - if err != nil { - t.Errorf("test %d: Unexpected error: %v", i, err) - continue - } - tr := NewReader(f) - for j, header := range test.headers { - hdr, err := tr.Next() - if err != nil || hdr == nil { - t.Errorf("test %d, entry %d: Didn't get entry: %v", i, j, err) - f.Close() - continue testLoop - } - if !reflect.DeepEqual(hdr, header) { - t.Errorf("test %d, entry %d: Incorrect header:\nhave %+v\nwant %+v", - i, j, *hdr, *header) - } - } - hdr, err := tr.Next() - if hdr != nil || err != nil { - t.Errorf("test %d: Unexpected entry or error: hdr=%v err=%v", i, err) - } - f.Close() - } -} - -// Respect line breaks in function calls. -func _() { - f(x) - f(x, - x) - f(x, - x, - ) - f( - x, - x) - f( - x, - x, - ) -} - -// Respect line breaks in function declarations. -func _(x T) {} -func _(x T, - y T) { -} -func _(x T, - y T, -) { -} -func _( - x T, - y T) { -} -func _( - x T, - y T, -) { -} - -// Example from issue #2597. -func ManageStatus0( - in <-chan *Status, - req <-chan Request, - stat chan<- *TargetInfo, - TargetHistorySize int) { -} - -func ManageStatus1( - in <-chan *Status, - req <-chan Request, - stat chan<- *TargetInfo, - TargetHistorySize int, -) { -} - -// Example from issue #9064. -func (y *y) xerrors() error { - _ = "xerror.test" //TODO- - _ = []byte(` -foo bar foo bar foo bar -`) //TODO- -} - -func _() { - _ = "abc" // foo - _ = `abc_0123456789_` // foo -} - -func _() { - _ = "abc" // foo - _ = `abc -0123456789 -` // foo -} - -// There should be exactly one linebreak after this comment. diff --git a/internal/backport/go/printer/testdata/linebreaks.input b/internal/backport/go/printer/testdata/linebreaks.input deleted file mode 100644 index 9e714f3eff..0000000000 --- a/internal/backport/go/printer/testdata/linebreaks.input +++ /dev/null @@ -1,291 +0,0 @@ -// Copyright 2009 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 linebreaks - -import ( - "bytes" - "fmt" - "io" - "os" - "reflect" - "strings" - "testing" -) - -type writerTestEntry struct { - header *Header - contents string -} - -type writerTest struct { - file string // filename of expected output - entries []*writerTestEntry -} - -var writerTests = []*writerTest{ - &writerTest{ - file: "testdata/writer.tar", - entries: []*writerTestEntry{ - &writerTestEntry{ - header: &Header{ - Name: "small.txt", - Mode: 0640, - Uid: 73025, - Gid: 5000, - Size: 5, - Mtime: 1246508266, - Typeflag: '0', - Uname: "dsymonds", - Gname: "eng", - }, - contents: "Kilts", - }, - &writerTestEntry{ - header: &Header{ - Name: "small2.txt", - Mode: 0640, - Uid: 73025, - Gid: 5000, - Size: 11, - Mtime: 1245217492, - Typeflag: '0', - Uname: "dsymonds", - Gname: "eng", - }, - contents: "Google.com\n", - }, - }, - }, - // The truncated test file was produced using these commands: - // dd if=/dev/zero bs=1048576 count=16384 > /tmp/16gig.txt - // tar -b 1 -c -f- /tmp/16gig.txt | dd bs=512 count=8 > writer-big.tar - &writerTest{ - file: "testdata/writer-big.tar", - entries: []*writerTestEntry{ - &writerTestEntry{ - header: &Header{ - Name: "tmp/16gig.txt", - Mode: 0640, - Uid: 73025, - Gid: 5000, - Size: 16 << 30, - Mtime: 1254699560, - Typeflag: '0', - Uname: "dsymonds", - Gname: "eng", - }, - // no contents - }, - }, - }, -} - -type untarTest struct { - file string - headers []*Header -} - -var untarTests = []*untarTest{ - &untarTest{ - file: "testdata/gnu.tar", - headers: []*Header{ - &Header{ - Name: "small.txt", - Mode: 0640, - Uid: 73025, - Gid: 5000, - Size: 5, - Mtime: 1244428340, - Typeflag: '0', - Uname: "dsymonds", - Gname: "eng", - }, - &Header{ - Name: "small2.txt", - Mode: 0640, - Uid: 73025, - Gid: 5000, - Size: 11, - Mtime: 1244436044, - Typeflag: '0', - Uname: "dsymonds", - Gname: "eng", - }, - }, - }, - &untarTest{ - file: "testdata/star.tar", - headers: []*Header{ - &Header{ - Name: "small.txt", - Mode: 0640, - Uid: 73025, - Gid: 5000, - Size: 5, - Mtime: 1244592783, - Typeflag: '0', - Uname: "dsymonds", - Gname: "eng", - Atime: 1244592783, - Ctime: 1244592783, - }, - &Header{ - Name: "small2.txt", - Mode: 0640, - Uid: 73025, - Gid: 5000, - Size: 11, - Mtime: 1244592783, - Typeflag: '0', - Uname: "dsymonds", - Gname: "eng", - Atime: 1244592783, - Ctime: 1244592783, - }, - }, - }, - &untarTest{ - file: "testdata/v7.tar", - headers: []*Header{ - &Header{ - Name: "small.txt", - Mode: 0444, - Uid: 73025, - Gid: 5000, - Size: 5, - Mtime: 1244593104, - Typeflag: '\x00', - }, - &Header{ - Name: "small2.txt", - Mode: 0444, - Uid: 73025, - Gid: 5000, - Size: 11, - Mtime: 1244593104, - Typeflag: '\x00', - }, - }, - }, -} - -var facts = map[int] string { - 0: "1", - 1: "1", - 2: "2", - 10: "3628800", - 20: "2432902008176640000", - 100: "933262154439441526816992388562667004907159682643816214685929" + - "638952175999932299156089414639761565182862536979208272237582" + - "51185210916864000000000000000000000000", -} - -func usage() { - fmt.Fprintf(os.Stderr, - // TODO(gri): the 2nd string of this string list should not be indented - "usage: godoc package [name ...]\n" + - " godoc -http=:6060\n") - flag.PrintDefaults() - os.Exit(2) -} - -func TestReader(t *testing.T) { -testLoop: - for i, test := range untarTests { - f, err := os.Open(test.file, os.O_RDONLY, 0444) - if err != nil { - t.Errorf("test %d: Unexpected error: %v", i, err) - continue - } - tr := NewReader(f) - for j, header := range test.headers { - hdr, err := tr.Next() - if err != nil || hdr == nil { - t.Errorf("test %d, entry %d: Didn't get entry: %v", i, j, err) - f.Close() - continue testLoop - } - if !reflect.DeepEqual(hdr, header) { - t.Errorf("test %d, entry %d: Incorrect header:\nhave %+v\nwant %+v", - i, j, *hdr, *header) - } - } - hdr, err := tr.Next() - if hdr != nil || err != nil { - t.Errorf("test %d: Unexpected entry or error: hdr=%v err=%v", i, err) - } - f.Close() - } -} - -// Respect line breaks in function calls. -func _() { - f(x) - f(x, - x) - f(x, - x, - ) - f( - x, - x) - f( - x, - x, - ) -} - -// Respect line breaks in function declarations. -func _(x T) {} -func _(x T, - y T) {} -func _(x T, - y T, -) {} -func _( - x T, - y T) {} -func _( - x T, - y T, -) {} - -// Example from issue #2597. -func ManageStatus0( - in <-chan *Status, - req <-chan Request, - stat chan<- *TargetInfo, - TargetHistorySize int) { -} - -func ManageStatus1( - in <-chan *Status, - req <-chan Request, - stat chan<- *TargetInfo, - TargetHistorySize int, -) { -} - -// Example from issue #9064. -func (y *y) xerrors() error { - _ = "xerror.test" //TODO- - _ = []byte(` -foo bar foo bar foo bar -`) //TODO- -} - -func _() { - _ = "abc" // foo - _ = `abc_0123456789_` // foo -} - -func _() { - _ = "abc" // foo - _ = `abc -0123456789 -` // foo -} - -// There should be exactly one linebreak after this comment. diff --git a/internal/backport/go/printer/testdata/parser.go b/internal/backport/go/printer/testdata/parser.go deleted file mode 100644 index 8479cb6ac0..0000000000 --- a/internal/backport/go/printer/testdata/parser.go +++ /dev/null @@ -1,2148 +0,0 @@ -// Copyright 2009 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 parser implements a parser for Go source files. Input may be -// provided in a variety of forms (see the various Parse* functions); the -// output is an abstract syntax tree (AST) representing the Go source. The -// parser is invoked through one of the Parse* functions. - -package parser - -import ( - "fmt" - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/scanner" - "golang.org/x/website/internal/backport/go/token" -) - -// The mode parameter to the Parse* functions is a set of flags (or 0). -// They control the amount of source code parsed and other optional -// parser functionality. -const ( - PackageClauseOnly uint = 1 << iota // parsing stops after package clause - ImportsOnly // parsing stops after import declarations - ParseComments // parse comments and add them to AST - Trace // print a trace of parsed productions - DeclarationErrors // report declaration errors -) - -// The parser structure holds the parser's internal state. -type parser struct { - file *token.File - scanner.ErrorVector - scanner scanner.Scanner - - // Tracing/debugging - mode uint // parsing mode - trace bool // == (mode & Trace != 0) - indent uint // indentation used for tracing output - - // Comments - comments []*ast.CommentGroup - leadComment *ast.CommentGroup // last lead comment - lineComment *ast.CommentGroup // last line comment - - // Next token - pos token.Pos // token position - tok token.Token // one token look-ahead - lit string // token literal - - // Non-syntactic parser control - exprLev int // < 0: in control clause, >= 0: in expression - - // Ordinary identifier scopes - pkgScope *ast.Scope // pkgScope.Outer == nil - topScope *ast.Scope // top-most scope; may be pkgScope - unresolved []*ast.Ident // unresolved identifiers - imports []*ast.ImportSpec // list of imports - - // Label scope - // (maintained by open/close LabelScope) - labelScope *ast.Scope // label scope for current function - targetStack [][]*ast.Ident // stack of unresolved labels -} - -// scannerMode returns the scanner mode bits given the parser's mode bits. -func scannerMode(mode uint) uint { - var m uint = scanner.InsertSemis - if mode&ParseComments != 0 { - m |= scanner.ScanComments - } - return m -} - -func (p *parser) init(fset *token.FileSet, filename string, src []byte, mode uint) { - p.file = fset.AddFile(filename, fset.Base(), len(src)) - p.scanner.Init(p.file, src, p, scannerMode(mode)) - - p.mode = mode - p.trace = mode&Trace != 0 // for convenience (p.trace is used frequently) - - p.next() - - // set up the pkgScope here (as opposed to in parseFile) because - // there are other parser entry points (ParseExpr, etc.) - p.openScope() - p.pkgScope = p.topScope - - // for the same reason, set up a label scope - p.openLabelScope() -} - -// ---------------------------------------------------------------------------- -// Scoping support - -func (p *parser) openScope() { - p.topScope = ast.NewScope(p.topScope) -} - -func (p *parser) closeScope() { - p.topScope = p.topScope.Outer -} - -func (p *parser) openLabelScope() { - p.labelScope = ast.NewScope(p.labelScope) - p.targetStack = append(p.targetStack, nil) -} - -func (p *parser) closeLabelScope() { - // resolve labels - n := len(p.targetStack) - 1 - scope := p.labelScope - for _, ident := range p.targetStack[n] { - ident.Obj = scope.Lookup(ident.Name) - if ident.Obj == nil && p.mode&DeclarationErrors != 0 { - p.error(ident.Pos(), fmt.Sprintf("label %s undefined", ident.Name)) - } - } - // pop label scope - p.targetStack = p.targetStack[0:n] - p.labelScope = p.labelScope.Outer -} - -func (p *parser) declare(decl interface{}, scope *ast.Scope, kind ast.ObjKind, idents ...*ast.Ident) { - for _, ident := range idents { - assert(ident.Obj == nil, "identifier already declared or resolved") - if ident.Name != "_" { - obj := ast.NewObj(kind, ident.Name) - // remember the corresponding declaration for redeclaration - // errors and global variable resolution/typechecking phase - obj.Decl = decl - if alt := scope.Insert(obj); alt != nil && p.mode&DeclarationErrors != 0 { - prevDecl := "" - if pos := alt.Pos(); pos.IsValid() { - prevDecl = fmt.Sprintf("\n\tprevious declaration at %s", p.file.Position(pos)) - } - p.error(ident.Pos(), fmt.Sprintf("%s redeclared in this block%s", ident.Name, prevDecl)) - } - ident.Obj = obj - } - } -} - -func (p *parser) shortVarDecl(idents []*ast.Ident) { - // Go spec: A short variable declaration may redeclare variables - // provided they were originally declared in the same block with - // the same type, and at least one of the non-blank variables is new. - n := 0 // number of new variables - for _, ident := range idents { - assert(ident.Obj == nil, "identifier already declared or resolved") - if ident.Name != "_" { - obj := ast.NewObj(ast.Var, ident.Name) - // short var declarations cannot have redeclaration errors - // and are not global => no need to remember the respective - // declaration - alt := p.topScope.Insert(obj) - if alt == nil { - n++ // new declaration - alt = obj - } - ident.Obj = alt - } - } - if n == 0 && p.mode&DeclarationErrors != 0 { - p.error(idents[0].Pos(), "no new variables on left side of :=") - } -} - -// The unresolved object is a sentinel to mark identifiers that have been added -// to the list of unresolved identifiers. The sentinel is only used for verifying -// internal consistency. -var unresolved = new(ast.Object) - -func (p *parser) resolve(x ast.Expr) { - // nothing to do if x is not an identifier or the blank identifier - ident, _ := x.(*ast.Ident) - if ident == nil { - return - } - assert(ident.Obj == nil, "identifier already declared or resolved") - if ident.Name == "_" { - return - } - // try to resolve the identifier - for s := p.topScope; s != nil; s = s.Outer { - if obj := s.Lookup(ident.Name); obj != nil { - ident.Obj = obj - return - } - } - // all local scopes are known, so any unresolved identifier - // must be found either in the file scope, package scope - // (perhaps in another file), or universe scope --- collect - // them so that they can be resolved later - ident.Obj = unresolved - p.unresolved = append(p.unresolved, ident) -} - -// ---------------------------------------------------------------------------- -// Parsing support - -func (p *parser) printTrace(a ...interface{}) { - const dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . " + - ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . " - const n = uint(len(dots)) - pos := p.file.Position(p.pos) - fmt.Printf("%5d:%3d: ", pos.Line, pos.Column) - i := 2 * p.indent - for ; i > n; i -= n { - fmt.Print(dots) - } - fmt.Print(dots[0:i]) - fmt.Println(a...) -} - -func trace(p *parser, msg string) *parser { - p.printTrace(msg, "(") - p.indent++ - return p -} - -// Usage pattern: defer un(trace(p, "...")); -func un(p *parser) { - p.indent-- - p.printTrace(")") -} - -// Advance to the next token. -func (p *parser) next0() { - // Because of one-token look-ahead, print the previous token - // when tracing as it provides a more readable output. The - // very first token (!p.pos.IsValid()) is not initialized - // (it is token.ILLEGAL), so don't print it. - if p.trace && p.pos.IsValid() { - s := p.tok.String() - switch { - case p.tok.IsLiteral(): - p.printTrace(s, p.lit) - case p.tok.IsOperator(), p.tok.IsKeyword(): - p.printTrace("\"" + s + "\"") - default: - p.printTrace(s) - } - } - - p.pos, p.tok, p.lit = p.scanner.Scan() -} - -// Consume a comment and return it and the line on which it ends. -func (p *parser) consumeComment() (comment *ast.Comment, endline int) { - // /*-style comments may end on a different line than where they start. - // Scan the comment for '\n' chars and adjust endline accordingly. - endline = p.file.Line(p.pos) - if p.lit[1] == '*' { - // don't use range here - no need to decode Unicode code points - for i := 0; i < len(p.lit); i++ { - if p.lit[i] == '\n' { - endline++ - } - } - } - - comment = &ast.Comment{p.pos, p.lit} - p.next0() - - return -} - -// Consume a group of adjacent comments, add it to the parser's -// comments list, and return it together with the line at which -// the last comment in the group ends. An empty line or non-comment -// token terminates a comment group. -func (p *parser) consumeCommentGroup() (comments *ast.CommentGroup, endline int) { - var list []*ast.Comment - endline = p.file.Line(p.pos) - for p.tok == token.COMMENT && endline+1 >= p.file.Line(p.pos) { - var comment *ast.Comment - comment, endline = p.consumeComment() - list = append(list, comment) - } - - // add comment group to the comments list - comments = &ast.CommentGroup{list} - p.comments = append(p.comments, comments) - - return -} - -// Advance to the next non-comment token. In the process, collect -// any comment groups encountered, and remember the last lead and -// line comments. -// -// A lead comment is a comment group that starts and ends in a -// line without any other tokens and that is followed by a non-comment -// token on the line immediately after the comment group. -// -// A line comment is a comment group that follows a non-comment -// token on the same line, and that has no tokens after it on the line -// where it ends. -// -// Lead and line comments may be considered documentation that is -// stored in the AST. -func (p *parser) next() { - p.leadComment = nil - p.lineComment = nil - line := p.file.Line(p.pos) // current line - p.next0() - - if p.tok == token.COMMENT { - var comment *ast.CommentGroup - var endline int - - if p.file.Line(p.pos) == line { - // The comment is on same line as the previous token; it - // cannot be a lead comment but may be a line comment. - comment, endline = p.consumeCommentGroup() - if p.file.Line(p.pos) != endline { - // The next token is on a different line, thus - // the last comment group is a line comment. - p.lineComment = comment - } - } - - // consume successor comments, if any - endline = -1 - for p.tok == token.COMMENT { - comment, endline = p.consumeCommentGroup() - } - - if endline+1 == p.file.Line(p.pos) { - // The next token is following on the line immediately after the - // comment group, thus the last comment group is a lead comment. - p.leadComment = comment - } - } -} - -func (p *parser) error(pos token.Pos, msg string) { - p.Error(p.file.Position(pos), msg) -} - -func (p *parser) errorExpected(pos token.Pos, msg string) { - msg = "expected " + msg - if pos == p.pos { - // the error happened at the current position; - // make the error message more specific - if p.tok == token.SEMICOLON && p.lit[0] == '\n' { - msg += ", found newline" - } else { - msg += ", found '" + p.tok.String() + "'" - if p.tok.IsLiteral() { - msg += " " + p.lit - } - } - } - p.error(pos, msg) -} - -func (p *parser) expect(tok token.Token) token.Pos { - pos := p.pos - if p.tok != tok { - p.errorExpected(pos, "'"+tok.String()+"'") - } - p.next() // make progress - return pos -} - -func (p *parser) expectSemi() { - if p.tok != token.RPAREN && p.tok != token.RBRACE { - p.expect(token.SEMICOLON) - } -} - -func assert(cond bool, msg string) { - if !cond { - panic("go/parser internal error: " + msg) - } -} - -// ---------------------------------------------------------------------------- -// Identifiers - -func (p *parser) parseIdent() *ast.Ident { - pos := p.pos - name := "_" - if p.tok == token.IDENT { - name = p.lit - p.next() - } else { - p.expect(token.IDENT) // use expect() error handling - } - return &ast.Ident{pos, name, nil} -} - -func (p *parser) parseIdentList() (list []*ast.Ident) { - if p.trace { - defer un(trace(p, "IdentList")) - } - - list = append(list, p.parseIdent()) - for p.tok == token.COMMA { - p.next() - list = append(list, p.parseIdent()) - } - - return -} - -// ---------------------------------------------------------------------------- -// Common productions - -// If lhs is set, result list elements which are identifiers are not resolved. -func (p *parser) parseExprList(lhs bool) (list []ast.Expr) { - if p.trace { - defer un(trace(p, "ExpressionList")) - } - - list = append(list, p.parseExpr(lhs)) - for p.tok == token.COMMA { - p.next() - list = append(list, p.parseExpr(lhs)) - } - - return -} - -func (p *parser) parseLhsList() []ast.Expr { - list := p.parseExprList(true) - switch p.tok { - case token.DEFINE: - // lhs of a short variable declaration - p.shortVarDecl(p.makeIdentList(list)) - case token.COLON: - // lhs of a label declaration or a communication clause of a select - // statement (parseLhsList is not called when parsing the case clause - // of a switch statement): - // - labels are declared by the caller of parseLhsList - // - for communication clauses, if there is a stand-alone identifier - // followed by a colon, we have a syntax error; there is no need - // to resolve the identifier in that case - default: - // identifiers must be declared elsewhere - for _, x := range list { - p.resolve(x) - } - } - return list -} - -func (p *parser) parseRhsList() []ast.Expr { - return p.parseExprList(false) -} - -// ---------------------------------------------------------------------------- -// Types - -func (p *parser) parseType() ast.Expr { - if p.trace { - defer un(trace(p, "Type")) - } - - typ := p.tryType() - - if typ == nil { - pos := p.pos - p.errorExpected(pos, "type") - p.next() // make progress - return &ast.BadExpr{pos, p.pos} - } - - return typ -} - -// If the result is an identifier, it is not resolved. -func (p *parser) parseTypeName() ast.Expr { - if p.trace { - defer un(trace(p, "TypeName")) - } - - ident := p.parseIdent() - // don't resolve ident yet - it may be a parameter or field name - - if p.tok == token.PERIOD { - // ident is a package name - p.next() - p.resolve(ident) - sel := p.parseIdent() - return &ast.SelectorExpr{ident, sel} - } - - return ident -} - -func (p *parser) parseArrayType(ellipsisOk bool) ast.Expr { - if p.trace { - defer un(trace(p, "ArrayType")) - } - - lbrack := p.expect(token.LBRACK) - var len ast.Expr - if ellipsisOk && p.tok == token.ELLIPSIS { - len = &ast.Ellipsis{p.pos, nil} - p.next() - } else if p.tok != token.RBRACK { - len = p.parseRhs() - } - p.expect(token.RBRACK) - elt := p.parseType() - - return &ast.ArrayType{lbrack, len, elt} -} - -func (p *parser) makeIdentList(list []ast.Expr) []*ast.Ident { - idents := make([]*ast.Ident, len(list)) - for i, x := range list { - ident, isIdent := x.(*ast.Ident) - if !isIdent { - pos := x.(ast.Expr).Pos() - p.errorExpected(pos, "identifier") - ident = &ast.Ident{pos, "_", nil} - } - idents[i] = ident - } - return idents -} - -func (p *parser) parseFieldDecl(scope *ast.Scope) *ast.Field { - if p.trace { - defer un(trace(p, "FieldDecl")) - } - - doc := p.leadComment - - // fields - list, typ := p.parseVarList(false) - - // optional tag - var tag *ast.BasicLit - if p.tok == token.STRING { - tag = &ast.BasicLit{p.pos, p.tok, p.lit} - p.next() - } - - // analyze case - var idents []*ast.Ident - if typ != nil { - // IdentifierList Type - idents = p.makeIdentList(list) - } else { - // ["*"] TypeName (AnonymousField) - typ = list[0] // we always have at least one element - p.resolve(typ) - if n := len(list); n > 1 || !isTypeName(deref(typ)) { - pos := typ.Pos() - p.errorExpected(pos, "anonymous field") - typ = &ast.BadExpr{pos, list[n-1].End()} - } - } - - p.expectSemi() // call before accessing p.linecomment - - field := &ast.Field{doc, idents, typ, tag, p.lineComment} - p.declare(field, scope, ast.Var, idents...) - - return field -} - -func (p *parser) parseStructType() *ast.StructType { - if p.trace { - defer un(trace(p, "StructType")) - } - - pos := p.expect(token.STRUCT) - lbrace := p.expect(token.LBRACE) - scope := ast.NewScope(nil) // struct scope - var list []*ast.Field - for p.tok == token.IDENT || p.tok == token.MUL || p.tok == token.LPAREN { - // a field declaration cannot start with a '(' but we accept - // it here for more robust parsing and better error messages - // (parseFieldDecl will check and complain if necessary) - list = append(list, p.parseFieldDecl(scope)) - } - rbrace := p.expect(token.RBRACE) - - // TODO(gri): store struct scope in AST - return &ast.StructType{pos, &ast.FieldList{lbrace, list, rbrace}, false} -} - -func (p *parser) parsePointerType() *ast.StarExpr { - if p.trace { - defer un(trace(p, "PointerType")) - } - - star := p.expect(token.MUL) - base := p.parseType() - - return &ast.StarExpr{star, base} -} - -func (p *parser) tryVarType(isParam bool) ast.Expr { - if isParam && p.tok == token.ELLIPSIS { - pos := p.pos - p.next() - typ := p.tryIdentOrType(isParam) // don't use parseType so we can provide better error message - if typ == nil { - p.error(pos, "'...' parameter is missing type") - typ = &ast.BadExpr{pos, p.pos} - } - if p.tok != token.RPAREN { - p.error(pos, "can use '...' with last parameter type only") - } - return &ast.Ellipsis{pos, typ} - } - return p.tryIdentOrType(false) -} - -func (p *parser) parseVarType(isParam bool) ast.Expr { - typ := p.tryVarType(isParam) - if typ == nil { - pos := p.pos - p.errorExpected(pos, "type") - p.next() // make progress - typ = &ast.BadExpr{pos, p.pos} - } - return typ -} - -func (p *parser) parseVarList(isParam bool) (list []ast.Expr, typ ast.Expr) { - if p.trace { - defer un(trace(p, "VarList")) - } - - // a list of identifiers looks like a list of type names - for { - // parseVarType accepts any type (including parenthesized ones) - // even though the syntax does not permit them here: we - // accept them all for more robust parsing and complain - // afterwards - list = append(list, p.parseVarType(isParam)) - if p.tok != token.COMMA { - break - } - p.next() - } - - // if we had a list of identifiers, it must be followed by a type - typ = p.tryVarType(isParam) - if typ != nil { - p.resolve(typ) - } - - return -} - -func (p *parser) parseParameterList(scope *ast.Scope, ellipsisOk bool) (params []*ast.Field) { - if p.trace { - defer un(trace(p, "ParameterList")) - } - - list, typ := p.parseVarList(ellipsisOk) - if typ != nil { - // IdentifierList Type - idents := p.makeIdentList(list) - field := &ast.Field{nil, idents, typ, nil, nil} - params = append(params, field) - // Go spec: The scope of an identifier denoting a function - // parameter or result variable is the function body. - p.declare(field, scope, ast.Var, idents...) - if p.tok == token.COMMA { - p.next() - } - - for p.tok != token.RPAREN && p.tok != token.EOF { - idents := p.parseIdentList() - typ := p.parseVarType(ellipsisOk) - field := &ast.Field{nil, idents, typ, nil, nil} - params = append(params, field) - // Go spec: The scope of an identifier denoting a function - // parameter or result variable is the function body. - p.declare(field, scope, ast.Var, idents...) - if p.tok != token.COMMA { - break - } - p.next() - } - - } else { - // Type { "," Type } (anonymous parameters) - params = make([]*ast.Field, len(list)) - for i, x := range list { - p.resolve(x) - params[i] = &ast.Field{Type: x} - } - } - - return -} - -func (p *parser) parseParameters(scope *ast.Scope, ellipsisOk bool) *ast.FieldList { - if p.trace { - defer un(trace(p, "Parameters")) - } - - var params []*ast.Field - lparen := p.expect(token.LPAREN) - if p.tok != token.RPAREN { - params = p.parseParameterList(scope, ellipsisOk) - } - rparen := p.expect(token.RPAREN) - - return &ast.FieldList{lparen, params, rparen} -} - -func (p *parser) parseResult(scope *ast.Scope) *ast.FieldList { - if p.trace { - defer un(trace(p, "Result")) - } - - if p.tok == token.LPAREN { - return p.parseParameters(scope, false) - } - - typ := p.tryType() - if typ != nil { - list := make([]*ast.Field, 1) - list[0] = &ast.Field{Type: typ} - return &ast.FieldList{List: list} - } - - return nil -} - -func (p *parser) parseSignature(scope *ast.Scope) (params, results *ast.FieldList) { - if p.trace { - defer un(trace(p, "Signature")) - } - - params = p.parseParameters(scope, true) - results = p.parseResult(scope) - - return -} - -func (p *parser) parseFuncType() (*ast.FuncType, *ast.Scope) { - if p.trace { - defer un(trace(p, "FuncType")) - } - - pos := p.expect(token.FUNC) - scope := ast.NewScope(p.topScope) // function scope - params, results := p.parseSignature(scope) - - return &ast.FuncType{pos, params, results}, scope -} - -func (p *parser) parseMethodSpec(scope *ast.Scope) *ast.Field { - if p.trace { - defer un(trace(p, "MethodSpec")) - } - - doc := p.leadComment - var idents []*ast.Ident - var typ ast.Expr - x := p.parseTypeName() - if ident, isIdent := x.(*ast.Ident); isIdent && p.tok == token.LPAREN { - // method - idents = []*ast.Ident{ident} - scope := ast.NewScope(nil) // method scope - params, results := p.parseSignature(scope) - typ = &ast.FuncType{token.NoPos, params, results} - } else { - // embedded interface - typ = x - } - p.expectSemi() // call before accessing p.linecomment - - spec := &ast.Field{doc, idents, typ, nil, p.lineComment} - p.declare(spec, scope, ast.Fun, idents...) - - return spec -} - -func (p *parser) parseInterfaceType() *ast.InterfaceType { - if p.trace { - defer un(trace(p, "InterfaceType")) - } - - pos := p.expect(token.INTERFACE) - lbrace := p.expect(token.LBRACE) - scope := ast.NewScope(nil) // interface scope - var list []*ast.Field - for p.tok == token.IDENT { - list = append(list, p.parseMethodSpec(scope)) - } - rbrace := p.expect(token.RBRACE) - - // TODO(gri): store interface scope in AST - return &ast.InterfaceType{pos, &ast.FieldList{lbrace, list, rbrace}, false} -} - -func (p *parser) parseMapType() *ast.MapType { - if p.trace { - defer un(trace(p, "MapType")) - } - - pos := p.expect(token.MAP) - p.expect(token.LBRACK) - key := p.parseType() - p.expect(token.RBRACK) - value := p.parseType() - - return &ast.MapType{pos, key, value} -} - -func (p *parser) parseChanType() *ast.ChanType { - if p.trace { - defer un(trace(p, "ChanType")) - } - - pos := p.pos - dir := ast.SEND | ast.RECV - if p.tok == token.CHAN { - p.next() - if p.tok == token.ARROW { - p.next() - dir = ast.SEND - } - } else { - p.expect(token.ARROW) - p.expect(token.CHAN) - dir = ast.RECV - } - value := p.parseType() - - return &ast.ChanType{pos, dir, value} -} - -// If the result is an identifier, it is not resolved. -func (p *parser) tryIdentOrType(ellipsisOk bool) ast.Expr { - switch p.tok { - case token.IDENT: - return p.parseTypeName() - case token.LBRACK: - return p.parseArrayType(ellipsisOk) - case token.STRUCT: - return p.parseStructType() - case token.MUL: - return p.parsePointerType() - case token.FUNC: - typ, _ := p.parseFuncType() - return typ - case token.INTERFACE: - return p.parseInterfaceType() - case token.MAP: - return p.parseMapType() - case token.CHAN, token.ARROW: - return p.parseChanType() - case token.LPAREN: - lparen := p.pos - p.next() - typ := p.parseType() - rparen := p.expect(token.RPAREN) - return &ast.ParenExpr{lparen, typ, rparen} - } - - // no type found - return nil -} - -func (p *parser) tryType() ast.Expr { - typ := p.tryIdentOrType(false) - if typ != nil { - p.resolve(typ) - } - return typ -} - -// ---------------------------------------------------------------------------- -// Blocks - -func (p *parser) parseStmtList() (list []ast.Stmt) { - if p.trace { - defer un(trace(p, "StatementList")) - } - - for p.tok != token.CASE && p.tok != token.DEFAULT && p.tok != token.RBRACE && p.tok != token.EOF { - list = append(list, p.parseStmt()) - } - - return -} - -func (p *parser) parseBody(scope *ast.Scope) *ast.BlockStmt { - if p.trace { - defer un(trace(p, "Body")) - } - - lbrace := p.expect(token.LBRACE) - p.topScope = scope // open function scope - p.openLabelScope() - list := p.parseStmtList() - p.closeLabelScope() - p.closeScope() - rbrace := p.expect(token.RBRACE) - - return &ast.BlockStmt{lbrace, list, rbrace} -} - -func (p *parser) parseBlockStmt() *ast.BlockStmt { - if p.trace { - defer un(trace(p, "BlockStmt")) - } - - lbrace := p.expect(token.LBRACE) - p.openScope() - list := p.parseStmtList() - p.closeScope() - rbrace := p.expect(token.RBRACE) - - return &ast.BlockStmt{lbrace, list, rbrace} -} - -// ---------------------------------------------------------------------------- -// Expressions - -func (p *parser) parseFuncTypeOrLit() ast.Expr { - if p.trace { - defer un(trace(p, "FuncTypeOrLit")) - } - - typ, scope := p.parseFuncType() - if p.tok != token.LBRACE { - // function type only - return typ - } - - p.exprLev++ - body := p.parseBody(scope) - p.exprLev-- - - return &ast.FuncLit{typ, body} -} - -// parseOperand may return an expression or a raw type (incl. array -// types of the form [...]T. Callers must verify the result. -// If lhs is set and the result is an identifier, it is not resolved. -func (p *parser) parseOperand(lhs bool) ast.Expr { - if p.trace { - defer un(trace(p, "Operand")) - } - - switch p.tok { - case token.IDENT: - x := p.parseIdent() - if !lhs { - p.resolve(x) - } - return x - - case token.INT, token.FLOAT, token.IMAG, token.CHAR, token.STRING: - x := &ast.BasicLit{p.pos, p.tok, p.lit} - p.next() - return x - - case token.LPAREN: - lparen := p.pos - p.next() - p.exprLev++ - x := p.parseRhs() - p.exprLev-- - rparen := p.expect(token.RPAREN) - return &ast.ParenExpr{lparen, x, rparen} - - case token.FUNC: - return p.parseFuncTypeOrLit() - - default: - if typ := p.tryIdentOrType(true); typ != nil { - // could be type for composite literal or conversion - _, isIdent := typ.(*ast.Ident) - assert(!isIdent, "type cannot be identifier") - return typ - } - } - - pos := p.pos - p.errorExpected(pos, "operand") - p.next() // make progress - return &ast.BadExpr{pos, p.pos} -} - -func (p *parser) parseSelector(x ast.Expr) ast.Expr { - if p.trace { - defer un(trace(p, "Selector")) - } - - sel := p.parseIdent() - - return &ast.SelectorExpr{x, sel} -} - -func (p *parser) parseTypeAssertion(x ast.Expr) ast.Expr { - if p.trace { - defer un(trace(p, "TypeAssertion")) - } - - p.expect(token.LPAREN) - var typ ast.Expr - if p.tok == token.TYPE { - // type switch: typ == nil - p.next() - } else { - typ = p.parseType() - } - p.expect(token.RPAREN) - - return &ast.TypeAssertExpr{x, typ} -} - -func (p *parser) parseIndexOrSlice(x ast.Expr) ast.Expr { - if p.trace { - defer un(trace(p, "IndexOrSlice")) - } - - lbrack := p.expect(token.LBRACK) - p.exprLev++ - var low, high ast.Expr - isSlice := false - if p.tok != token.COLON { - low = p.parseRhs() - } - if p.tok == token.COLON { - isSlice = true - p.next() - if p.tok != token.RBRACK { - high = p.parseRhs() - } - } - p.exprLev-- - rbrack := p.expect(token.RBRACK) - - if isSlice { - return &ast.SliceExpr{x, lbrack, low, high, rbrack} - } - return &ast.IndexExpr{x, lbrack, low, rbrack} -} - -func (p *parser) parseCallOrConversion(fun ast.Expr) *ast.CallExpr { - if p.trace { - defer un(trace(p, "CallOrConversion")) - } - - lparen := p.expect(token.LPAREN) - p.exprLev++ - var list []ast.Expr - var ellipsis token.Pos - for p.tok != token.RPAREN && p.tok != token.EOF && !ellipsis.IsValid() { - list = append(list, p.parseRhs()) - if p.tok == token.ELLIPSIS { - ellipsis = p.pos - p.next() - } - if p.tok != token.COMMA { - break - } - p.next() - } - p.exprLev-- - rparen := p.expect(token.RPAREN) - - return &ast.CallExpr{fun, lparen, list, ellipsis, rparen} -} - -func (p *parser) parseElement(keyOk bool) ast.Expr { - if p.trace { - defer un(trace(p, "Element")) - } - - if p.tok == token.LBRACE { - return p.parseLiteralValue(nil) - } - - x := p.parseExpr(keyOk) // don't resolve if map key - if keyOk { - if p.tok == token.COLON { - colon := p.pos - p.next() - return &ast.KeyValueExpr{x, colon, p.parseElement(false)} - } - p.resolve(x) // not a map key - } - - return x -} - -func (p *parser) parseElementList() (list []ast.Expr) { - if p.trace { - defer un(trace(p, "ElementList")) - } - - for p.tok != token.RBRACE && p.tok != token.EOF { - list = append(list, p.parseElement(true)) - if p.tok != token.COMMA { - break - } - p.next() - } - - return -} - -func (p *parser) parseLiteralValue(typ ast.Expr) ast.Expr { - if p.trace { - defer un(trace(p, "LiteralValue")) - } - - lbrace := p.expect(token.LBRACE) - var elts []ast.Expr - p.exprLev++ - if p.tok != token.RBRACE { - elts = p.parseElementList() - } - p.exprLev-- - rbrace := p.expect(token.RBRACE) - return &ast.CompositeLit{typ, lbrace, elts, rbrace} -} - -// checkExpr checks that x is an expression (and not a type). -func (p *parser) checkExpr(x ast.Expr) ast.Expr { - switch t := unparen(x).(type) { - case *ast.BadExpr: - case *ast.Ident: - case *ast.BasicLit: - case *ast.FuncLit: - case *ast.CompositeLit: - case *ast.ParenExpr: - panic("unreachable") - case *ast.SelectorExpr: - case *ast.IndexExpr: - case *ast.SliceExpr: - case *ast.TypeAssertExpr: - if t.Type == nil { - // the form X.(type) is only allowed in type switch expressions - p.errorExpected(x.Pos(), "expression") - x = &ast.BadExpr{x.Pos(), x.End()} - } - case *ast.CallExpr: - case *ast.StarExpr: - case *ast.UnaryExpr: - if t.Op == token.RANGE { - // the range operator is only allowed at the top of a for statement - p.errorExpected(x.Pos(), "expression") - x = &ast.BadExpr{x.Pos(), x.End()} - } - case *ast.BinaryExpr: - default: - // all other nodes are not proper expressions - p.errorExpected(x.Pos(), "expression") - x = &ast.BadExpr{x.Pos(), x.End()} - } - return x -} - -// isTypeName reports whether x is a (qualified) TypeName. -func isTypeName(x ast.Expr) bool { - switch t := x.(type) { - case *ast.BadExpr: - case *ast.Ident: - case *ast.SelectorExpr: - _, isIdent := t.X.(*ast.Ident) - return isIdent - default: - return false // all other nodes are not type names - } - return true -} - -// isLiteralType reports whether x is a legal composite literal type. -func isLiteralType(x ast.Expr) bool { - switch t := x.(type) { - case *ast.BadExpr: - case *ast.Ident: - case *ast.SelectorExpr: - _, isIdent := t.X.(*ast.Ident) - return isIdent - case *ast.ArrayType: - case *ast.StructType: - case *ast.MapType: - default: - return false // all other nodes are not legal composite literal types - } - return true -} - -// If x is of the form *T, deref returns T, otherwise it returns x. -func deref(x ast.Expr) ast.Expr { - if p, isPtr := x.(*ast.StarExpr); isPtr { - x = p.X - } - return x -} - -// If x is of the form (T), unparen returns unparen(T), otherwise it returns x. -func unparen(x ast.Expr) ast.Expr { - if p, isParen := x.(*ast.ParenExpr); isParen { - x = unparen(p.X) - } - return x -} - -// checkExprOrType checks that x is an expression or a type -// (and not a raw type such as [...]T). -func (p *parser) checkExprOrType(x ast.Expr) ast.Expr { - switch t := unparen(x).(type) { - case *ast.ParenExpr: - panic("unreachable") - case *ast.UnaryExpr: - if t.Op == token.RANGE { - // the range operator is only allowed at the top of a for statement - p.errorExpected(x.Pos(), "expression") - x = &ast.BadExpr{x.Pos(), x.End()} - } - case *ast.ArrayType: - if len, isEllipsis := t.Len.(*ast.Ellipsis); isEllipsis { - p.error(len.Pos(), "expected array length, found '...'") - x = &ast.BadExpr{x.Pos(), x.End()} - } - } - - // all other nodes are expressions or types - return x -} - -// If lhs is set and the result is an identifier, it is not resolved. -func (p *parser) parsePrimaryExpr(lhs bool) ast.Expr { - if p.trace { - defer un(trace(p, "PrimaryExpr")) - } - - x := p.parseOperand(lhs) -L: - for { - switch p.tok { - case token.PERIOD: - p.next() - if lhs { - p.resolve(x) - } - switch p.tok { - case token.IDENT: - x = p.parseSelector(p.checkExpr(x)) - case token.LPAREN: - x = p.parseTypeAssertion(p.checkExpr(x)) - default: - pos := p.pos - p.next() // make progress - p.errorExpected(pos, "selector or type assertion") - x = &ast.BadExpr{pos, p.pos} - } - case token.LBRACK: - if lhs { - p.resolve(x) - } - x = p.parseIndexOrSlice(p.checkExpr(x)) - case token.LPAREN: - if lhs { - p.resolve(x) - } - x = p.parseCallOrConversion(p.checkExprOrType(x)) - case token.LBRACE: - if isLiteralType(x) && (p.exprLev >= 0 || !isTypeName(x)) { - if lhs { - p.resolve(x) - } - x = p.parseLiteralValue(x) - } else { - break L - } - default: - break L - } - lhs = false // no need to try to resolve again - } - - return x -} - -// If lhs is set and the result is an identifier, it is not resolved. -func (p *parser) parseUnaryExpr(lhs bool) ast.Expr { - if p.trace { - defer un(trace(p, "UnaryExpr")) - } - - switch p.tok { - case token.ADD, token.SUB, token.NOT, token.XOR, token.AND, token.RANGE: - pos, op := p.pos, p.tok - p.next() - x := p.parseUnaryExpr(false) - return &ast.UnaryExpr{pos, op, p.checkExpr(x)} - - case token.ARROW: - // channel type or receive expression - pos := p.pos - p.next() - if p.tok == token.CHAN { - p.next() - value := p.parseType() - return &ast.ChanType{pos, ast.RECV, value} - } - - x := p.parseUnaryExpr(false) - return &ast.UnaryExpr{pos, token.ARROW, p.checkExpr(x)} - - case token.MUL: - // pointer type or unary "*" expression - pos := p.pos - p.next() - x := p.parseUnaryExpr(false) - return &ast.StarExpr{pos, p.checkExprOrType(x)} - } - - return p.parsePrimaryExpr(lhs) -} - -// If lhs is set and the result is an identifier, it is not resolved. -func (p *parser) parseBinaryExpr(lhs bool, prec1 int) ast.Expr { - if p.trace { - defer un(trace(p, "BinaryExpr")) - } - - x := p.parseUnaryExpr(lhs) - for prec := p.tok.Precedence(); prec >= prec1; prec-- { - for p.tok.Precedence() == prec { - pos, op := p.pos, p.tok - p.next() - if lhs { - p.resolve(x) - lhs = false - } - y := p.parseBinaryExpr(false, prec+1) - x = &ast.BinaryExpr{p.checkExpr(x), pos, op, p.checkExpr(y)} - } - } - - return x -} - -// If lhs is set and the result is an identifier, it is not resolved. -// TODO(gri): parseExpr may return a type or even a raw type ([..]int) - -// should reject when a type/raw type is obviously not allowed -func (p *parser) parseExpr(lhs bool) ast.Expr { - if p.trace { - defer un(trace(p, "Expression")) - } - - return p.parseBinaryExpr(lhs, token.LowestPrec+1) -} - -func (p *parser) parseRhs() ast.Expr { - return p.parseExpr(false) -} - -// ---------------------------------------------------------------------------- -// Statements - -func (p *parser) parseSimpleStmt(labelOk bool) ast.Stmt { - if p.trace { - defer un(trace(p, "SimpleStmt")) - } - - x := p.parseLhsList() - - switch p.tok { - case - token.DEFINE, token.ASSIGN, token.ADD_ASSIGN, - token.SUB_ASSIGN, token.MUL_ASSIGN, token.QUO_ASSIGN, - token.REM_ASSIGN, token.AND_ASSIGN, token.OR_ASSIGN, - token.XOR_ASSIGN, token.SHL_ASSIGN, token.SHR_ASSIGN, token.AND_NOT_ASSIGN: - // assignment statement - pos, tok := p.pos, p.tok - p.next() - y := p.parseRhsList() - return &ast.AssignStmt{x, pos, tok, y} - } - - if len(x) > 1 { - p.errorExpected(x[0].Pos(), "1 expression") - // continue with first expression - } - - switch p.tok { - case token.COLON: - // labeled statement - colon := p.pos - p.next() - if label, isIdent := x[0].(*ast.Ident); labelOk && isIdent { - // Go spec: The scope of a label is the body of the function - // in which it is declared and excludes the body of any nested - // function. - stmt := &ast.LabeledStmt{label, colon, p.parseStmt()} - p.declare(stmt, p.labelScope, ast.Lbl, label) - return stmt - } - p.error(x[0].Pos(), "illegal label declaration") - return &ast.BadStmt{x[0].Pos(), colon + 1} - - case token.ARROW: - // send statement - arrow := p.pos - p.next() // consume "<-" - y := p.parseRhs() - return &ast.SendStmt{x[0], arrow, y} - - case token.INC, token.DEC: - // increment or decrement - s := &ast.IncDecStmt{x[0], p.pos, p.tok} - p.next() // consume "++" or "--" - return s - } - - // expression - return &ast.ExprStmt{x[0]} -} - -func (p *parser) parseCallExpr() *ast.CallExpr { - x := p.parseRhs() - if call, isCall := x.(*ast.CallExpr); isCall { - return call - } - p.errorExpected(x.Pos(), "function/method call") - return nil -} - -func (p *parser) parseGoStmt() ast.Stmt { - if p.trace { - defer un(trace(p, "GoStmt")) - } - - pos := p.expect(token.GO) - call := p.parseCallExpr() - p.expectSemi() - if call == nil { - return &ast.BadStmt{pos, pos + 2} // len("go") - } - - return &ast.GoStmt{pos, call} -} - -func (p *parser) parseDeferStmt() ast.Stmt { - if p.trace { - defer un(trace(p, "DeferStmt")) - } - - pos := p.expect(token.DEFER) - call := p.parseCallExpr() - p.expectSemi() - if call == nil { - return &ast.BadStmt{pos, pos + 5} // len("defer") - } - - return &ast.DeferStmt{pos, call} -} - -func (p *parser) parseReturnStmt() *ast.ReturnStmt { - if p.trace { - defer un(trace(p, "ReturnStmt")) - } - - pos := p.pos - p.expect(token.RETURN) - var x []ast.Expr - if p.tok != token.SEMICOLON && p.tok != token.RBRACE { - x = p.parseRhsList() - } - p.expectSemi() - - return &ast.ReturnStmt{pos, x} -} - -func (p *parser) parseBranchStmt(tok token.Token) *ast.BranchStmt { - if p.trace { - defer un(trace(p, "BranchStmt")) - } - - pos := p.expect(tok) - var label *ast.Ident - if tok != token.FALLTHROUGH && p.tok == token.IDENT { - label = p.parseIdent() - // add to list of unresolved targets - n := len(p.targetStack) - 1 - p.targetStack[n] = append(p.targetStack[n], label) - } - p.expectSemi() - - return &ast.BranchStmt{pos, tok, label} -} - -func (p *parser) makeExpr(s ast.Stmt) ast.Expr { - if s == nil { - return nil - } - if es, isExpr := s.(*ast.ExprStmt); isExpr { - return p.checkExpr(es.X) - } - p.error(s.Pos(), "expected condition, found simple statement") - return &ast.BadExpr{s.Pos(), s.End()} -} - -func (p *parser) parseIfStmt() *ast.IfStmt { - if p.trace { - defer un(trace(p, "IfStmt")) - } - - pos := p.expect(token.IF) - p.openScope() - defer p.closeScope() - - var s ast.Stmt - var x ast.Expr - { - prevLev := p.exprLev - p.exprLev = -1 - if p.tok == token.SEMICOLON { - p.next() - x = p.parseRhs() - } else { - s = p.parseSimpleStmt(false) - if p.tok == token.SEMICOLON { - p.next() - x = p.parseRhs() - } else { - x = p.makeExpr(s) - s = nil - } - } - p.exprLev = prevLev - } - - body := p.parseBlockStmt() - var else_ ast.Stmt - if p.tok == token.ELSE { - p.next() - else_ = p.parseStmt() - } else { - p.expectSemi() - } - - return &ast.IfStmt{pos, s, x, body, else_} -} - -func (p *parser) parseTypeList() (list []ast.Expr) { - if p.trace { - defer un(trace(p, "TypeList")) - } - - list = append(list, p.parseType()) - for p.tok == token.COMMA { - p.next() - list = append(list, p.parseType()) - } - - return -} - -func (p *parser) parseCaseClause(exprSwitch bool) *ast.CaseClause { - if p.trace { - defer un(trace(p, "CaseClause")) - } - - pos := p.pos - var list []ast.Expr - if p.tok == token.CASE { - p.next() - if exprSwitch { - list = p.parseRhsList() - } else { - list = p.parseTypeList() - } - } else { - p.expect(token.DEFAULT) - } - - colon := p.expect(token.COLON) - p.openScope() - body := p.parseStmtList() - p.closeScope() - - return &ast.CaseClause{pos, list, colon, body} -} - -func isExprSwitch(s ast.Stmt) bool { - if s == nil { - return true - } - if e, ok := s.(*ast.ExprStmt); ok { - if a, ok := e.X.(*ast.TypeAssertExpr); ok { - return a.Type != nil // regular type assertion - } - return true - } - return false -} - -func (p *parser) parseSwitchStmt() ast.Stmt { - if p.trace { - defer un(trace(p, "SwitchStmt")) - } - - pos := p.expect(token.SWITCH) - p.openScope() - defer p.closeScope() - - var s1, s2 ast.Stmt - if p.tok != token.LBRACE { - prevLev := p.exprLev - p.exprLev = -1 - if p.tok != token.SEMICOLON { - s2 = p.parseSimpleStmt(false) - } - if p.tok == token.SEMICOLON { - p.next() - s1 = s2 - s2 = nil - if p.tok != token.LBRACE { - s2 = p.parseSimpleStmt(false) - } - } - p.exprLev = prevLev - } - - exprSwitch := isExprSwitch(s2) - lbrace := p.expect(token.LBRACE) - var list []ast.Stmt - for p.tok == token.CASE || p.tok == token.DEFAULT { - list = append(list, p.parseCaseClause(exprSwitch)) - } - rbrace := p.expect(token.RBRACE) - p.expectSemi() - body := &ast.BlockStmt{lbrace, list, rbrace} - - if exprSwitch { - return &ast.SwitchStmt{pos, s1, p.makeExpr(s2), body} - } - // type switch - // TODO(gri): do all the checks! - return &ast.TypeSwitchStmt{pos, s1, s2, body} -} - -func (p *parser) parseCommClause() *ast.CommClause { - if p.trace { - defer un(trace(p, "CommClause")) - } - - p.openScope() - pos := p.pos - var comm ast.Stmt - if p.tok == token.CASE { - p.next() - lhs := p.parseLhsList() - if p.tok == token.ARROW { - // SendStmt - if len(lhs) > 1 { - p.errorExpected(lhs[0].Pos(), "1 expression") - // continue with first expression - } - arrow := p.pos - p.next() - rhs := p.parseRhs() - comm = &ast.SendStmt{lhs[0], arrow, rhs} - } else { - // RecvStmt - pos := p.pos - tok := p.tok - var rhs ast.Expr - if tok == token.ASSIGN || tok == token.DEFINE { - // RecvStmt with assignment - if len(lhs) > 2 { - p.errorExpected(lhs[0].Pos(), "1 or 2 expressions") - // continue with first two expressions - lhs = lhs[0:2] - } - p.next() - rhs = p.parseRhs() - } else { - // rhs must be single receive operation - if len(lhs) > 1 { - p.errorExpected(lhs[0].Pos(), "1 expression") - // continue with first expression - } - rhs = lhs[0] - lhs = nil // there is no lhs - } - if x, isUnary := rhs.(*ast.UnaryExpr); !isUnary || x.Op != token.ARROW { - p.errorExpected(rhs.Pos(), "send or receive operation") - rhs = &ast.BadExpr{rhs.Pos(), rhs.End()} - } - if lhs != nil { - comm = &ast.AssignStmt{lhs, pos, tok, []ast.Expr{rhs}} - } else { - comm = &ast.ExprStmt{rhs} - } - } - } else { - p.expect(token.DEFAULT) - } - - colon := p.expect(token.COLON) - body := p.parseStmtList() - p.closeScope() - - return &ast.CommClause{pos, comm, colon, body} -} - -func (p *parser) parseSelectStmt() *ast.SelectStmt { - if p.trace { - defer un(trace(p, "SelectStmt")) - } - - pos := p.expect(token.SELECT) - lbrace := p.expect(token.LBRACE) - var list []ast.Stmt - for p.tok == token.CASE || p.tok == token.DEFAULT { - list = append(list, p.parseCommClause()) - } - rbrace := p.expect(token.RBRACE) - p.expectSemi() - body := &ast.BlockStmt{lbrace, list, rbrace} - - return &ast.SelectStmt{pos, body} -} - -func (p *parser) parseForStmt() ast.Stmt { - if p.trace { - defer un(trace(p, "ForStmt")) - } - - pos := p.expect(token.FOR) - p.openScope() - defer p.closeScope() - - var s1, s2, s3 ast.Stmt - if p.tok != token.LBRACE { - prevLev := p.exprLev - p.exprLev = -1 - if p.tok != token.SEMICOLON { - s2 = p.parseSimpleStmt(false) - } - if p.tok == token.SEMICOLON { - p.next() - s1 = s2 - s2 = nil - if p.tok != token.SEMICOLON { - s2 = p.parseSimpleStmt(false) - } - p.expectSemi() - if p.tok != token.LBRACE { - s3 = p.parseSimpleStmt(false) - } - } - p.exprLev = prevLev - } - - body := p.parseBlockStmt() - p.expectSemi() - - if as, isAssign := s2.(*ast.AssignStmt); isAssign { - // possibly a for statement with a range clause; check assignment operator - if as.Tok != token.ASSIGN && as.Tok != token.DEFINE { - p.errorExpected(as.TokPos, "'=' or ':='") - return &ast.BadStmt{pos, body.End()} - } - // check lhs - var key, value ast.Expr - switch len(as.Lhs) { - case 2: - key, value = as.Lhs[0], as.Lhs[1] - case 1: - key = as.Lhs[0] - default: - p.errorExpected(as.Lhs[0].Pos(), "1 or 2 expressions") - return &ast.BadStmt{pos, body.End()} - } - // check rhs - if len(as.Rhs) != 1 { - p.errorExpected(as.Rhs[0].Pos(), "1 expression") - return &ast.BadStmt{pos, body.End()} - } - if rhs, isUnary := as.Rhs[0].(*ast.UnaryExpr); isUnary && rhs.Op == token.RANGE { - // rhs is range expression - // (any short variable declaration was handled by parseSimpleStat above) - return &ast.RangeStmt{pos, key, value, as.TokPos, as.Tok, rhs.X, body} - } - p.errorExpected(s2.Pos(), "range clause") - return &ast.BadStmt{pos, body.End()} - } - - // regular for statement - return &ast.ForStmt{pos, s1, p.makeExpr(s2), s3, body} -} - -func (p *parser) parseStmt() (s ast.Stmt) { - if p.trace { - defer un(trace(p, "Statement")) - } - - switch p.tok { - case token.CONST, token.TYPE, token.VAR: - s = &ast.DeclStmt{p.parseDecl()} - case - // tokens that may start a top-level expression - token.IDENT, token.INT, token.FLOAT, token.CHAR, token.STRING, token.FUNC, token.LPAREN, // operand - token.LBRACK, token.STRUCT, // composite type - token.MUL, token.AND, token.ARROW, token.ADD, token.SUB, token.XOR: // unary operators - s = p.parseSimpleStmt(true) - // because of the required look-ahead, labeled statements are - // parsed by parseSimpleStmt - don't expect a semicolon after - // them - if _, isLabeledStmt := s.(*ast.LabeledStmt); !isLabeledStmt { - p.expectSemi() - } - case token.GO: - s = p.parseGoStmt() - case token.DEFER: - s = p.parseDeferStmt() - case token.RETURN: - s = p.parseReturnStmt() - case token.BREAK, token.CONTINUE, token.GOTO, token.FALLTHROUGH: - s = p.parseBranchStmt(p.tok) - case token.LBRACE: - s = p.parseBlockStmt() - p.expectSemi() - case token.IF: - s = p.parseIfStmt() - case token.SWITCH: - s = p.parseSwitchStmt() - case token.SELECT: - s = p.parseSelectStmt() - case token.FOR: - s = p.parseForStmt() - case token.SEMICOLON: - s = &ast.EmptyStmt{p.pos} - p.next() - case token.RBRACE: - // a semicolon may be omitted before a closing "}" - s = &ast.EmptyStmt{p.pos} - default: - // no statement found - pos := p.pos - p.errorExpected(pos, "statement") - p.next() // make progress - s = &ast.BadStmt{pos, p.pos} - } - - return -} - -// ---------------------------------------------------------------------------- -// Declarations - -type parseSpecFunction func(p *parser, doc *ast.CommentGroup, iota int) ast.Spec - -func parseImportSpec(p *parser, doc *ast.CommentGroup, _ int) ast.Spec { - if p.trace { - defer un(trace(p, "ImportSpec")) - } - - var ident *ast.Ident - switch p.tok { - case token.PERIOD: - ident = &ast.Ident{p.pos, ".", nil} - p.next() - case token.IDENT: - ident = p.parseIdent() - } - - var path *ast.BasicLit - if p.tok == token.STRING { - path = &ast.BasicLit{p.pos, p.tok, p.lit} - p.next() - } else { - p.expect(token.STRING) // use expect() error handling - } - p.expectSemi() // call before accessing p.linecomment - - // collect imports - spec := &ast.ImportSpec{doc, ident, path, p.lineComment} - p.imports = append(p.imports, spec) - - return spec -} - -func parseConstSpec(p *parser, doc *ast.CommentGroup, iota int) ast.Spec { - if p.trace { - defer un(trace(p, "ConstSpec")) - } - - idents := p.parseIdentList() - typ := p.tryType() - var values []ast.Expr - if typ != nil || p.tok == token.ASSIGN || iota == 0 { - p.expect(token.ASSIGN) - values = p.parseRhsList() - } - p.expectSemi() // call before accessing p.linecomment - - // Go spec: The scope of a constant or variable identifier declared inside - // a function begins at the end of the ConstSpec or VarSpec and ends at - // the end of the innermost containing block. - // (Global identifiers are resolved in a separate phase after parsing.) - spec := &ast.ValueSpec{doc, idents, typ, values, p.lineComment} - p.declare(spec, p.topScope, ast.Con, idents...) - - return spec -} - -func parseTypeSpec(p *parser, doc *ast.CommentGroup, _ int) ast.Spec { - if p.trace { - defer un(trace(p, "TypeSpec")) - } - - ident := p.parseIdent() - - // Go spec: The scope of a type identifier declared inside a function begins - // at the identifier in the TypeSpec and ends at the end of the innermost - // containing block. - // (Global identifiers are resolved in a separate phase after parsing.) - spec := &ast.TypeSpec{doc, ident, nil, nil} - p.declare(spec, p.topScope, ast.Typ, ident) - - spec.Type = p.parseType() - p.expectSemi() // call before accessing p.linecomment - spec.Comment = p.lineComment - - return spec -} - -func parseVarSpec(p *parser, doc *ast.CommentGroup, _ int) ast.Spec { - if p.trace { - defer un(trace(p, "VarSpec")) - } - - idents := p.parseIdentList() - typ := p.tryType() - var values []ast.Expr - if typ == nil || p.tok == token.ASSIGN { - p.expect(token.ASSIGN) - values = p.parseRhsList() - } - p.expectSemi() // call before accessing p.linecomment - - // Go spec: The scope of a constant or variable identifier declared inside - // a function begins at the end of the ConstSpec or VarSpec and ends at - // the end of the innermost containing block. - // (Global identifiers are resolved in a separate phase after parsing.) - spec := &ast.ValueSpec{doc, idents, typ, values, p.lineComment} - p.declare(spec, p.topScope, ast.Var, idents...) - - return spec -} - -func (p *parser) parseGenDecl(keyword token.Token, f parseSpecFunction) *ast.GenDecl { - if p.trace { - defer un(trace(p, "GenDecl("+keyword.String()+")")) - } - - doc := p.leadComment - pos := p.expect(keyword) - var lparen, rparen token.Pos - var list []ast.Spec - if p.tok == token.LPAREN { - lparen = p.pos - p.next() - for iota := 0; p.tok != token.RPAREN && p.tok != token.EOF; iota++ { - list = append(list, f(p, p.leadComment, iota)) - } - rparen = p.expect(token.RPAREN) - p.expectSemi() - } else { - list = append(list, f(p, nil, 0)) - } - - return &ast.GenDecl{doc, pos, keyword, lparen, list, rparen} -} - -func (p *parser) parseReceiver(scope *ast.Scope) *ast.FieldList { - if p.trace { - defer un(trace(p, "Receiver")) - } - - pos := p.pos - par := p.parseParameters(scope, false) - - // must have exactly one receiver - if par.NumFields() != 1 { - p.errorExpected(pos, "exactly one receiver") - // TODO determine a better range for BadExpr below - par.List = []*ast.Field{{Type: &ast.BadExpr{pos, pos}}} - return par - } - - // recv type must be of the form ["*"] identifier - recv := par.List[0] - base := deref(recv.Type) - if _, isIdent := base.(*ast.Ident); !isIdent { - p.errorExpected(base.Pos(), "(unqualified) identifier") - par.List = []*ast.Field{{Type: &ast.BadExpr{recv.Pos(), recv.End()}}} - } - - return par -} - -func (p *parser) parseFuncDecl() *ast.FuncDecl { - if p.trace { - defer un(trace(p, "FunctionDecl")) - } - - doc := p.leadComment - pos := p.expect(token.FUNC) - scope := ast.NewScope(p.topScope) // function scope - - var recv *ast.FieldList - if p.tok == token.LPAREN { - recv = p.parseReceiver(scope) - } - - ident := p.parseIdent() - - params, results := p.parseSignature(scope) - - var body *ast.BlockStmt - if p.tok == token.LBRACE { - body = p.parseBody(scope) - } - p.expectSemi() - - decl := &ast.FuncDecl{doc, recv, ident, &ast.FuncType{pos, params, results}, body} - if recv == nil { - // Go spec: The scope of an identifier denoting a constant, type, - // variable, or function (but not method) declared at top level - // (outside any function) is the package block. - // - // init() functions cannot be referred to and there may - // be more than one - don't put them in the pkgScope - if ident.Name != "init" { - p.declare(decl, p.pkgScope, ast.Fun, ident) - } - } - - return decl -} - -func (p *parser) parseDecl() ast.Decl { - if p.trace { - defer un(trace(p, "Declaration")) - } - - var f parseSpecFunction - switch p.tok { - case token.CONST: - f = parseConstSpec - - case token.TYPE: - f = parseTypeSpec - - case token.VAR: - f = parseVarSpec - - case token.FUNC: - return p.parseFuncDecl() - - default: - pos := p.pos - p.errorExpected(pos, "declaration") - p.next() // make progress - decl := &ast.BadDecl{pos, p.pos} - return decl - } - - return p.parseGenDecl(p.tok, f) -} - -func (p *parser) parseDeclList() (list []ast.Decl) { - if p.trace { - defer un(trace(p, "DeclList")) - } - - for p.tok != token.EOF { - list = append(list, p.parseDecl()) - } - - return -} - -// ---------------------------------------------------------------------------- -// Source files - -func (p *parser) parseFile() *ast.File { - if p.trace { - defer un(trace(p, "File")) - } - - // package clause - doc := p.leadComment - pos := p.expect(token.PACKAGE) - // Go spec: The package clause is not a declaration; - // the package name does not appear in any scope. - ident := p.parseIdent() - if ident.Name == "_" { - p.error(p.pos, "invalid package name _") - } - p.expectSemi() - - var decls []ast.Decl - - // Don't bother parsing the rest if we had errors already. - // Likely not a Go source file at all. - - if p.ErrorCount() == 0 && p.mode&PackageClauseOnly == 0 { - // import decls - for p.tok == token.IMPORT { - decls = append(decls, p.parseGenDecl(token.IMPORT, parseImportSpec)) - } - - if p.mode&ImportsOnly == 0 { - // rest of package body - for p.tok != token.EOF { - decls = append(decls, p.parseDecl()) - } - } - } - - assert(p.topScope == p.pkgScope, "imbalanced scopes") - - // resolve global identifiers within the same file - i := 0 - for _, ident := range p.unresolved { - // i <= index for current ident - assert(ident.Obj == unresolved, "object already resolved") - ident.Obj = p.pkgScope.Lookup(ident.Name) // also removes unresolved sentinel - if ident.Obj == nil { - p.unresolved[i] = ident - i++ - } - } - - // TODO(gri): store p.imports in AST - return &ast.File{doc, pos, ident, decls, p.pkgScope, p.imports, p.unresolved[0:i], p.comments} -} diff --git a/internal/backport/go/printer/testdata/slow.golden b/internal/backport/go/printer/testdata/slow.golden deleted file mode 100644 index 43a15cb1d0..0000000000 --- a/internal/backport/go/printer/testdata/slow.golden +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2011 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 deepequal_test - -import ( - "testing" - "google3/spam/archer/frontend/deepequal" -) - -func TestTwoNilValues(t *testing.T) { - if err := deepequal.Check(nil, nil); err != nil { - t.Errorf("expected nil, saw %v", err) - } -} - -type Foo struct { - bar *Bar - bang *Bar -} - -type Bar struct { - baz *Baz - foo []*Foo -} - -type Baz struct { - entries map[int]interface{} - whatever string -} - -func newFoo() *Foo { - return &Foo{bar: &Bar{baz: &Baz{ - entries: map[int]interface{}{ - 42: &Foo{}, - 21: &Bar{}, - 11: &Baz{whatever: "it's just a test"}}}}, - bang: &Bar{foo: []*Foo{ - &Foo{bar: &Bar{baz: &Baz{ - entries: map[int]interface{}{ - 43: &Foo{}, - 22: &Bar{}, - 13: &Baz{whatever: "this is nuts"}}}}, - bang: &Bar{foo: []*Foo{ - &Foo{bar: &Bar{baz: &Baz{ - entries: map[int]interface{}{ - 61: &Foo{}, - 71: &Bar{}, - 11: &Baz{whatever: "no, it's Go"}}}}, - bang: &Bar{foo: []*Foo{ - &Foo{bar: &Bar{baz: &Baz{ - entries: map[int]interface{}{ - 0: &Foo{}, - -2: &Bar{}, - -11: &Baz{whatever: "we need to go deeper"}}}}, - bang: &Bar{foo: []*Foo{ - &Foo{bar: &Bar{baz: &Baz{ - entries: map[int]interface{}{ - -2: &Foo{}, - -5: &Bar{}, - -7: &Baz{whatever: "are you serious?"}}}}, - bang: &Bar{foo: []*Foo{}}}, - &Foo{bar: &Bar{baz: &Baz{ - entries: map[int]interface{}{ - -100: &Foo{}, - 50: &Bar{}, - 20: &Baz{whatever: "na, not really ..."}}}}, - bang: &Bar{foo: []*Foo{}}}}}}}}}, - &Foo{bar: &Bar{baz: &Baz{ - entries: map[int]interface{}{ - 2: &Foo{}, - 1: &Bar{}, - -1: &Baz{whatever: "... it's just a test."}}}}, - bang: &Bar{foo: []*Foo{}}}}}}}}} -} - -func TestElaborate(t *testing.T) { - a := newFoo() - b := newFoo() - - if err := deepequal.Check(a, b); err != nil { - t.Errorf("expected nil, saw %v", err) - } -} diff --git a/internal/backport/go/printer/testdata/slow.input b/internal/backport/go/printer/testdata/slow.input deleted file mode 100644 index 0e5a23d886..0000000000 --- a/internal/backport/go/printer/testdata/slow.input +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2011 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 deepequal_test - -import ( - "testing" - "google3/spam/archer/frontend/deepequal" -) - -func TestTwoNilValues(t *testing.T) { - if err := deepequal.Check(nil, nil); err != nil { - t.Errorf("expected nil, saw %v", err) - } -} - -type Foo struct { - bar *Bar - bang *Bar -} - -type Bar struct { - baz *Baz - foo []*Foo -} - -type Baz struct { - entries map[int]interface{} - whatever string -} - -func newFoo() (*Foo) { -return &Foo{bar: &Bar{ baz: &Baz{ -entries: map[int]interface{}{ -42: &Foo{}, -21: &Bar{}, -11: &Baz{ whatever: "it's just a test" }}}}, - bang: &Bar{foo: []*Foo{ -&Foo{bar: &Bar{ baz: &Baz{ -entries: map[int]interface{}{ -43: &Foo{}, -22: &Bar{}, -13: &Baz{ whatever: "this is nuts" }}}}, - bang: &Bar{foo: []*Foo{ -&Foo{bar: &Bar{ baz: &Baz{ -entries: map[int]interface{}{ -61: &Foo{}, -71: &Bar{}, -11: &Baz{ whatever: "no, it's Go" }}}}, - bang: &Bar{foo: []*Foo{ -&Foo{bar: &Bar{ baz: &Baz{ -entries: map[int]interface{}{ -0: &Foo{}, --2: &Bar{}, --11: &Baz{ whatever: "we need to go deeper" }}}}, - bang: &Bar{foo: []*Foo{ -&Foo{bar: &Bar{ baz: &Baz{ -entries: map[int]interface{}{ --2: &Foo{}, --5: &Bar{}, --7: &Baz{ whatever: "are you serious?" }}}}, - bang: &Bar{foo: []*Foo{}}}, -&Foo{bar: &Bar{ baz: &Baz{ -entries: map[int]interface{}{ --100: &Foo{}, -50: &Bar{}, -20: &Baz{ whatever: "na, not really ..." }}}}, - bang: &Bar{foo: []*Foo{}}}}}}}}}, -&Foo{bar: &Bar{ baz: &Baz{ -entries: map[int]interface{}{ -2: &Foo{}, -1: &Bar{}, --1: &Baz{ whatever: "... it's just a test." }}}}, - bang: &Bar{foo: []*Foo{}}}}}}}}} -} - -func TestElaborate(t *testing.T) { - a := newFoo() - b := newFoo() - - if err := deepequal.Check(a, b); err != nil { - t.Errorf("expected nil, saw %v", err) - } -} diff --git a/internal/backport/go/printer/testdata/statements.golden b/internal/backport/go/printer/testdata/statements.golden deleted file mode 100644 index 4b13460473..0000000000 --- a/internal/backport/go/printer/testdata/statements.golden +++ /dev/null @@ -1,644 +0,0 @@ -// Copyright 2009 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 statements - -var expr bool - -func use(x interface{}) {} - -// Formatting of multi-line return statements. -func _f() { - return - return x, y, z - return T{} - return T{1, 2, 3}, - x, y, z - return T{1, 2, 3}, - x, y, - z - return T{1, - 2, - 3} - return T{1, - 2, - 3, - } - return T{ - 1, - 2, - 3} - return T{ - 1, - 2, - 3, - } - return T{ - 1, - T{1, 2, 3}, - 3, - } - return T{ - 1, - T{1, - 2, 3}, - 3, - } - return T{ - 1, - T{1, - 2, - 3}, - 3, - } - return T{ - 1, - 2, - }, nil - return T{ - 1, - 2, - }, - T{ - x: 3, - y: 4, - }, nil - return T{ - 1, - 2, - }, - nil - return T{ - 1, - 2, - }, - T{ - x: 3, - y: 4, - }, - nil - return x + y + - z - return func() {} - return func() { - _ = 0 - }, T{ - 1, 2, - } - return func() { - _ = 0 - } - return func() T { - return T{ - 1, 2, - } - } -} - -// Formatting of multi-line returns: test cases from issue 1207. -func F() (*T, os.Error) { - return &T{ - X: 1, - Y: 2, - }, - nil -} - -func G() (*T, *T, os.Error) { - return &T{ - X: 1, - Y: 2, - }, - &T{ - X: 3, - Y: 4, - }, - nil -} - -func _() interface{} { - return &fileStat{ - name: basename(file.name), - size: mkSize(d.FileSizeHigh, d.FileSizeLow), - modTime: mkModTime(d.LastWriteTime), - mode: mkMode(d.FileAttributes), - sys: mkSysFromFI(&d), - }, nil -} - -// Formatting of if-statement headers. -func _() { - if true { - } - if true { - } // no semicolon printed - if expr { - } - if expr { - } // no semicolon printed - if expr { - } // no parens printed - if expr { - } // no semicolon and parens printed - if x := expr; true { - use(x) - } - if x := expr; expr { - use(x) - } -} - -// Formatting of switch-statement headers. -func _() { - switch { - } - switch { - } // no semicolon printed - switch expr { - } - switch expr { - } // no semicolon printed - switch expr { - } // no parens printed - switch expr { - } // no semicolon and parens printed - switch x := expr; { - default: - use( - x) - } - switch x := expr; expr { - default: - use(x) - } -} - -// Formatting of switch statement bodies. -func _() { - switch { - } - - switch x := 0; x { - case 1: - use(x) - use(x) // followed by an empty line - - case 2: // followed by an empty line - - use(x) // followed by an empty line - - case 3: // no empty lines - use(x) - use(x) - } - - switch x { - case 0: - use(x) - case 1: // this comment should have no effect on the previous or next line - use(x) - } - - switch x := 0; x { - case 1: - x = 0 - // this comment should be indented - case 2: - x = 0 - // this comment should not be indented, it is aligned with the next case - case 3: - x = 0 - /* indented comment - aligned - aligned - */ - // bla - /* and more */ - case 4: - x = 0 - /* not indented comment - aligned - aligned - */ - // bla - /* and more */ - case 5: - } -} - -// Formatting of selected select statements. -func _() { - select {} - select { /* this comment should not be tab-aligned because the closing } is on the same line */ - } - select { /* this comment should be tab-aligned */ - } - select { // this comment should be tab-aligned - } - select { - case <-c: - } -} - -// Formatting of for-statement headers for single-line for-loops. -func _() { - for { - } - for expr { - } - for expr { - } // no parens printed - for { - } // no semicolons printed - for x := expr; ; { - use(x) - } - for expr { - } // no semicolons printed - for expr { - } // no semicolons and parens printed - for ; ; expr = false { - } - for x := expr; expr; { - use(x) - } - for x := expr; ; expr = false { - use(x) - } - for ; expr; expr = false { - } - for x := expr; expr; expr = false { - use(x) - } - for x := range []int{} { - use(x) - } - for x := range []int{} { - use(x) - } // no parens printed -} - -// Formatting of for-statement headers for multi-line for-loops. -func _() { - for { - } - for expr { - } - for expr { - } // no parens printed - for { - } // no semicolons printed - for x := expr; ; { - use(x) - } - for expr { - } // no semicolons printed - for expr { - } // no semicolons and parens printed - for ; ; expr = false { - } - for x := expr; expr; { - use(x) - } - for x := expr; ; expr = false { - use(x) - } - for ; expr; expr = false { - } - for x := expr; expr; expr = false { - use(x) - } - for range []int{} { - println("foo") - } - for x := range []int{} { - use(x) - } - for x := range []int{} { - use(x) - } // no parens printed -} - -// Formatting of selected short single- and multi-line statements. -func _() { - if cond { - } - if cond { - } // multiple lines - if cond { - } else { - } // else clause always requires multiple lines - - for { - } - for i := 0; i < len(a); 1++ { - } - for i := 0; i < len(a); 1++ { - a[i] = i - } - for i := 0; i < len(a); 1++ { - a[i] = i - } // multiple lines - - for range a { - } - for _ = range a { - } - for _, _ = range a { - } - for i := range a { - } - for i := range a { - a[i] = i - } - for i := range a { - a[i] = i - } // multiple lines - - go func() { - for { - a <- <-b - } - }() - defer func() { - if x := recover(); x != nil { - err = fmt.Sprintf("error: %s", x.msg) - } - }() -} - -// Don't remove mandatory parentheses around composite literals in control clauses. -func _() { - // strip parentheses - no composite literals or composite literals don't start with a type name - if x { - } - if x { - } - if []T{} { - } - if []T{} { - } - if []T{} { - } - - for x { - } - for x { - } - for []T{} { - } - for []T{} { - } - for []T{} { - } - - switch x { - } - switch x { - } - switch []T{} { - } - switch []T{} { - } - - for _ = range []T{T{42}} { - } - - // leave parentheses - composite literals start with a type name - if (T{}) { - } - if (T{}) { - } - if (T{}) { - } - - for (T{}) { - } - for (T{}) { - } - for (T{}) { - } - - switch (T{}) { - } - switch (T{}) { - } - - for _ = range (T1{T{42}}) { - } - - if x == (T{42}[0]) { - } - if (x == T{42}[0]) { - } - if x == (T{42}[0]) { - } - if x == (T{42}[0]) { - } - if x == (T{42}[0]) { - } - if x == a+b*(T{42}[0]) { - } - if (x == a+b*T{42}[0]) { - } - if x == a+b*(T{42}[0]) { - } - if x == a+(b*(T{42}[0])) { - } - if x == a+b*(T{42}[0]) { - } - if (a + b*(T{42}[0])) == x { - } - if (a + b*(T{42}[0])) == x { - } - - if struct{ x bool }{false}.x { - } - if (struct{ x bool }{false}.x) == false { - } - if struct{ x bool }{false}.x == false { - } -} - -// Extra empty lines inside functions. Do respect source code line -// breaks between statement boundaries but print at most one empty -// line at a time. -func _() { - - const _ = 0 - - const _ = 1 - type _ int - type _ float - - var _ = 0 - var x = 1 - - // Each use(x) call below should have at most one empty line before and after. - // Known bug: The first use call may have more than one empty line before - // (see go/printer/nodes.go, func linebreak). - - use(x) - - if x < x { - - use(x) - - } else { - - use(x) - - } -} - -// Formatting around labels. -func _() { -L: -} - -func _() { - // this comment should be indented -L: // no semicolon needed -} - -func _() { - switch 0 { - case 0: - L0: - ; // semicolon required - case 1: - L1: - ; // semicolon required - default: - L2: // no semicolon needed - } -} - -func _() { - f() -L1: - f() -L2: - ; -L3: -} - -func _() { - // this comment should be indented -L: -} - -func _() { -L: - _ = 0 -} - -func _() { - // this comment should be indented -L: - _ = 0 -} - -func _() { - for { - L1: - _ = 0 - L2: - _ = 0 - } -} - -func _() { - // this comment should be indented - for { - L1: - _ = 0 - L2: - _ = 0 - } -} - -func _() { - if true { - _ = 0 - } - _ = 0 // the indentation here should not be affected by the long label name -AnOverlongLabel: - _ = 0 - - if true { - _ = 0 - } - _ = 0 - -L: - _ = 0 -} - -func _() { - for { - goto L - } -L: - - MoreCode() -} - -func _() { - for { - goto L - } -L: // A comment on the same line as the label, followed by a single empty line. - // Known bug: There may be more than one empty line before MoreCode() - // (see go/printer/nodes.go, func linebreak). - - MoreCode() -} - -func _() { - for { - goto L - } -L: - - // There should be a single empty line before this comment. - MoreCode() -} - -func _() { - for { - goto AVeryLongLabelThatShouldNotAffectFormatting - } -AVeryLongLabelThatShouldNotAffectFormatting: - // There should be a single empty line after this comment. - - // There should be a single empty line before this comment. - MoreCode() -} - -// Formatting of empty statements. -func _() { - -} - -func _() { -} - -func _() { -} - -func _() { - f() -} - -func _() { -L: - ; -} - -func _() { -L: - ; - f() -} diff --git a/internal/backport/go/printer/testdata/statements.input b/internal/backport/go/printer/testdata/statements.input deleted file mode 100644 index cade1576bf..0000000000 --- a/internal/backport/go/printer/testdata/statements.input +++ /dev/null @@ -1,555 +0,0 @@ -// Copyright 2009 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 statements - -var expr bool - -func use(x interface{}) {} - -// Formatting of multi-line return statements. -func _f() { - return - return x, y, z - return T{} - return T{1, 2, 3}, - x, y, z - return T{1, 2, 3}, - x, y, - z - return T{1, - 2, - 3} - return T{1, - 2, - 3, - } - return T{ - 1, - 2, - 3} - return T{ - 1, - 2, - 3, - } - return T{ - 1, - T{1, 2, 3}, - 3, - } - return T{ - 1, - T{1, - 2, 3}, - 3, - } - return T{ - 1, - T{1, - 2, - 3}, - 3, - } - return T{ - 1, - 2, - }, nil - return T{ - 1, - 2, - }, - T{ - x: 3, - y: 4, - }, nil - return T{ - 1, - 2, - }, - nil - return T{ - 1, - 2, - }, - T{ - x: 3, - y: 4, - }, - nil - return x + y + - z - return func() {} - return func() { - _ = 0 - }, T{ - 1, 2, - } - return func() { - _ = 0 - } - return func() T { - return T { - 1, 2, - } - } -} - -// Formatting of multi-line returns: test cases from issue 1207. -func F() (*T, os.Error) { - return &T{ - X: 1, - Y: 2, - }, - nil -} - -func G() (*T, *T, os.Error) { - return &T{ - X: 1, - Y: 2, - }, - &T{ - X: 3, - Y: 4, - }, - nil -} - -func _() interface{} { - return &fileStat{ - name: basename(file.name), - size: mkSize(d.FileSizeHigh, d.FileSizeLow), - modTime: mkModTime(d.LastWriteTime), - mode: mkMode(d.FileAttributes), - sys: mkSysFromFI(&d), - }, nil -} - -// Formatting of if-statement headers. -func _() { - if true {} - if; true {} // no semicolon printed - if expr{} - if;expr{} // no semicolon printed - if (expr){} // no parens printed - if;((expr)){} // no semicolon and parens printed - if x:=expr;true{ - use(x)} - if x:=expr; expr {use(x)} -} - - -// Formatting of switch-statement headers. -func _() { - switch {} - switch;{} // no semicolon printed - switch expr {} - switch;expr{} // no semicolon printed - switch (expr) {} // no parens printed - switch;((expr)){} // no semicolon and parens printed - switch x := expr; { default:use( -x) - } - switch x := expr; expr {default:use(x)} -} - - -// Formatting of switch statement bodies. -func _() { - switch { - } - - switch x := 0; x { - case 1: - use(x) - use(x) // followed by an empty line - - case 2: // followed by an empty line - - use(x) // followed by an empty line - - case 3: // no empty lines - use(x) - use(x) - } - - switch x { - case 0: - use(x) - case 1: // this comment should have no effect on the previous or next line - use(x) - } - - switch x := 0; x { - case 1: - x = 0 - // this comment should be indented - case 2: - x = 0 - // this comment should not be indented, it is aligned with the next case - case 3: - x = 0 - /* indented comment - aligned - aligned - */ - // bla - /* and more */ - case 4: - x = 0 - /* not indented comment - aligned - aligned - */ - // bla - /* and more */ - case 5: - } -} - - -// Formatting of selected select statements. -func _() { - select { - } - select { /* this comment should not be tab-aligned because the closing } is on the same line */ } - select { /* this comment should be tab-aligned */ - } - select { // this comment should be tab-aligned - } - select { case <-c: } -} - - -// Formatting of for-statement headers for single-line for-loops. -func _() { - for{} - for expr {} - for (expr) {} // no parens printed - for;;{} // no semicolons printed - for x :=expr;; {use( x)} - for; expr;{} // no semicolons printed - for; ((expr));{} // no semicolons and parens printed - for; ; expr = false {} - for x :=expr; expr; {use(x)} - for x := expr;; expr=false {use(x)} - for;expr;expr =false {} - for x := expr;expr;expr = false { use(x) } - for x := range []int{} { use(x) } - for x := range (([]int{})) { use(x) } // no parens printed -} - - -// Formatting of for-statement headers for multi-line for-loops. -func _() { - for{ - } - for expr { - } - for (expr) { - } // no parens printed - for;;{ - } // no semicolons printed - for x :=expr;; {use( x) - } - for; expr;{ - } // no semicolons printed - for; ((expr));{ - } // no semicolons and parens printed - for; ; expr = false { - } - for x :=expr; expr; {use(x) - } - for x := expr;; expr=false {use(x) - } - for;expr;expr =false { - } - for x := expr;expr;expr = false { - use(x) - } - for range []int{} { - println("foo")} - for x := range []int{} { - use(x) } - for x := range (([]int{})) { - use(x) } // no parens printed -} - - -// Formatting of selected short single- and multi-line statements. -func _() { - if cond {} - if cond { - } // multiple lines - if cond {} else {} // else clause always requires multiple lines - - for {} - for i := 0; i < len(a); 1++ {} - for i := 0; i < len(a); 1++ { a[i] = i } - for i := 0; i < len(a); 1++ { a[i] = i - } // multiple lines - - for range a{} - for _ = range a{} - for _, _ = range a{} - for i := range a {} - for i := range a { a[i] = i } - for i := range a { a[i] = i - } // multiple lines - - go func() { for { a <- <-b } }() - defer func() { if x := recover(); x != nil { err = fmt.Sprintf("error: %s", x.msg) } }() -} - - -// Don't remove mandatory parentheses around composite literals in control clauses. -func _() { - // strip parentheses - no composite literals or composite literals don't start with a type name - if (x) {} - if (((x))) {} - if ([]T{}) {} - if (([]T{})) {} - if ; (((([]T{})))) {} - - for (x) {} - for (((x))) {} - for ([]T{}) {} - for (([]T{})) {} - for ; (((([]T{})))) ; {} - - switch (x) {} - switch (((x))) {} - switch ([]T{}) {} - switch ; (((([]T{})))) {} - - for _ = range ((([]T{T{42}}))) {} - - // leave parentheses - composite literals start with a type name - if (T{}) {} - if ((T{})) {} - if ; ((((T{})))) {} - - for (T{}) {} - for ((T{})) {} - for ; ((((T{})))) ; {} - - switch (T{}) {} - switch ; ((((T{})))) {} - - for _ = range (((T1{T{42}}))) {} - - if x == (T{42}[0]) {} - if (x == T{42}[0]) {} - if (x == (T{42}[0])) {} - if (x == (((T{42}[0])))) {} - if (((x == (T{42}[0])))) {} - if x == a + b*(T{42}[0]) {} - if (x == a + b*T{42}[0]) {} - if (x == a + b*(T{42}[0])) {} - if (x == a + ((b * (T{42}[0])))) {} - if (((x == a + b * (T{42}[0])))) {} - if (((a + b * (T{42}[0])) == x)) {} - if (((a + b * (T{42}[0])))) == x {} - - if (struct{x bool}{false}.x) {} - if (struct{x bool}{false}.x) == false {} - if (struct{x bool}{false}.x == false) {} -} - - -// Extra empty lines inside functions. Do respect source code line -// breaks between statement boundaries but print at most one empty -// line at a time. -func _() { - - const _ = 0 - - const _ = 1 - type _ int - type _ float - - var _ = 0 - var x = 1 - - // Each use(x) call below should have at most one empty line before and after. - // Known bug: The first use call may have more than one empty line before - // (see go/printer/nodes.go, func linebreak). - - - - use(x) - - if x < x { - - use(x) - - } else { - - use(x) - - } -} - - -// Formatting around labels. -func _() { - L: -} - - -func _() { - // this comment should be indented - L: ; // no semicolon needed -} - - -func _() { - switch 0 { - case 0: - L0: ; // semicolon required - case 1: - L1: ; // semicolon required - default: - L2: ; // no semicolon needed - } -} - - -func _() { - f() -L1: - f() -L2: - ; -L3: -} - - -func _() { - // this comment should be indented - L: -} - - -func _() { - L: _ = 0 -} - - -func _() { - // this comment should be indented - L: _ = 0 -} - - -func _() { - for { - L1: _ = 0 - L2: - _ = 0 - } -} - - -func _() { - // this comment should be indented - for { - L1: _ = 0 - L2: - _ = 0 - } -} - - -func _() { - if true { - _ = 0 - } - _ = 0 // the indentation here should not be affected by the long label name -AnOverlongLabel: - _ = 0 - - if true { - _ = 0 - } - _ = 0 - -L: _ = 0 -} - - -func _() { - for { - goto L - } -L: - - MoreCode() -} - - -func _() { - for { - goto L - } -L: // A comment on the same line as the label, followed by a single empty line. - // Known bug: There may be more than one empty line before MoreCode() - // (see go/printer/nodes.go, func linebreak). - - - - - MoreCode() -} - - -func _() { - for { - goto L - } -L: - - - - - // There should be a single empty line before this comment. - MoreCode() -} - - -func _() { - for { - goto AVeryLongLabelThatShouldNotAffectFormatting - } -AVeryLongLabelThatShouldNotAffectFormatting: - // There should be a single empty line after this comment. - - // There should be a single empty line before this comment. - MoreCode() -} - - -// Formatting of empty statements. -func _() { - ;;;;;;;;;;;;;;;;;;;;;;;;; -} - -func _() {;;;;;;;;;;;;;;;;;;;;;;;;; -} - -func _() {;;;;;;;;;;;;;;;;;;;;;;;;;} - -func _() { -f();;;;;;;;;;;;;;;;;;;;;;;;; -} - -func _() { -L:;;;;;;;;;;;; -} - -func _() { -L:;;;;;;;;;;;; - f() -} diff --git a/internal/backport/go/scanner/errors.go b/internal/backport/go/scanner/errors.go deleted file mode 100644 index 539bbfd636..0000000000 --- a/internal/backport/go/scanner/errors.go +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright 2009 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 scanner - -import ( - "fmt" - "golang.org/x/website/internal/backport/go/token" - "io" - "sort" -) - -// In an ErrorList, an error is represented by an *Error. -// The position Pos, if valid, points to the beginning of -// the offending token, and the error condition is described -// by Msg. -type Error struct { - Pos token.Position - Msg string -} - -// Error implements the error interface. -func (e Error) Error() string { - if e.Pos.Filename != "" || e.Pos.IsValid() { - // don't print "" - // TODO(gri) reconsider the semantics of Position.IsValid - return e.Pos.String() + ": " + e.Msg - } - return e.Msg -} - -// ErrorList is a list of *Errors. -// The zero value for an ErrorList is an empty ErrorList ready to use. -type ErrorList []*Error - -// Add adds an Error with given position and error message to an ErrorList. -func (p *ErrorList) Add(pos token.Position, msg string) { - *p = append(*p, &Error{pos, msg}) -} - -// Reset resets an ErrorList to no errors. -func (p *ErrorList) Reset() { *p = (*p)[0:0] } - -// ErrorList implements the sort Interface. -func (p ErrorList) Len() int { return len(p) } -func (p ErrorList) Swap(i, j int) { p[i], p[j] = p[j], p[i] } - -func (p ErrorList) Less(i, j int) bool { - e := &p[i].Pos - f := &p[j].Pos - // Note that it is not sufficient to simply compare file offsets because - // the offsets do not reflect modified line information (through //line - // comments). - if e.Filename != f.Filename { - return e.Filename < f.Filename - } - if e.Line != f.Line { - return e.Line < f.Line - } - if e.Column != f.Column { - return e.Column < f.Column - } - return p[i].Msg < p[j].Msg -} - -// Sort sorts an ErrorList. *Error entries are sorted by position, -// other errors are sorted by error message, and before any *Error -// entry. -func (p ErrorList) Sort() { - sort.Sort(p) -} - -// RemoveMultiples sorts an ErrorList and removes all but the first error per line. -func (p *ErrorList) RemoveMultiples() { - sort.Sort(p) - var last token.Position // initial last.Line is != any legal error line - i := 0 - for _, e := range *p { - if e.Pos.Filename != last.Filename || e.Pos.Line != last.Line { - last = e.Pos - (*p)[i] = e - i++ - } - } - *p = (*p)[0:i] -} - -// An ErrorList implements the error interface. -func (p ErrorList) Error() string { - switch len(p) { - case 0: - return "no errors" - case 1: - return p[0].Error() - } - return fmt.Sprintf("%s (and %d more errors)", p[0], len(p)-1) -} - -// Err returns an error equivalent to this error list. -// If the list is empty, Err returns nil. -func (p ErrorList) Err() error { - if len(p) == 0 { - return nil - } - return p -} - -// PrintError is a utility function that prints a list of errors to w, -// one error per line, if the err parameter is an ErrorList. Otherwise -// it prints the err string. -func PrintError(w io.Writer, err error) { - if list, ok := err.(ErrorList); ok { - for _, e := range list { - fmt.Fprintf(w, "%s\n", e) - } - } else if err != nil { - fmt.Fprintf(w, "%s\n", err) - } -} diff --git a/internal/backport/go/scanner/example_test.go b/internal/backport/go/scanner/example_test.go deleted file mode 100644 index e9092379a1..0000000000 --- a/internal/backport/go/scanner/example_test.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2012 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 scanner_test - -import ( - "fmt" - "golang.org/x/website/internal/backport/go/scanner" - "golang.org/x/website/internal/backport/go/token" -) - -func ExampleScanner_Scan() { - // src is the input that we want to tokenize. - src := []byte("cos(x) + 1i*sin(x) // Euler") - - // Initialize the scanner. - var s scanner.Scanner - fset := token.NewFileSet() // positions are relative to fset - file := fset.AddFile("", fset.Base(), len(src)) // register input "file" - s.Init(file, src, nil /* no error handler */, scanner.ScanComments) - - // Repeated calls to Scan yield the token sequence found in the input. - for { - pos, tok, lit := s.Scan() - if tok == token.EOF { - break - } - fmt.Printf("%s\t%s\t%q\n", fset.Position(pos), tok, lit) - } - - // output: - // 1:1 IDENT "cos" - // 1:4 ( "" - // 1:5 IDENT "x" - // 1:6 ) "" - // 1:8 + "" - // 1:10 IMAG "1i" - // 1:12 * "" - // 1:13 IDENT "sin" - // 1:16 ( "" - // 1:17 IDENT "x" - // 1:18 ) "" - // 1:20 ; "\n" - // 1:20 COMMENT "// Euler" -} diff --git a/internal/backport/go/scanner/scanner.go b/internal/backport/go/scanner/scanner.go deleted file mode 100644 index f96569e417..0000000000 --- a/internal/backport/go/scanner/scanner.go +++ /dev/null @@ -1,983 +0,0 @@ -// Copyright 2009 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 scanner implements a scanner for Go source text. -// It takes a []byte as source which can then be tokenized -// through repeated calls to the Scan method. -package scanner - -import ( - "bytes" - "fmt" - "golang.org/x/website/internal/backport/go/token" - "path/filepath" - "strconv" - "unicode" - "unicode/utf8" -) - -// An ErrorHandler may be provided to Scanner.Init. If a syntax error is -// encountered and a handler was installed, the handler is called with a -// position and an error message. The position points to the beginning of -// the offending token. -type ErrorHandler func(pos token.Position, msg string) - -// A Scanner holds the scanner's internal state while processing -// a given text. It can be allocated as part of another data -// structure but must be initialized via Init before use. -type Scanner struct { - // immutable state - file *token.File // source file handle - dir string // directory portion of file.Name() - src []byte // source - err ErrorHandler // error reporting; or nil - mode Mode // scanning mode - - // scanning state - ch rune // current character - offset int // character offset - rdOffset int // reading offset (position after current character) - lineOffset int // current line offset - insertSemi bool // insert a semicolon before next newline - - // public state - ok to modify - ErrorCount int // number of errors encountered -} - -const ( - bom = 0xFEFF // byte order mark, only permitted as very first character - eof = -1 // end of file -) - -// Read the next Unicode char into s.ch. -// s.ch < 0 means end-of-file. -// -// For optimization, there is some overlap between this method and -// s.scanIdentifier. -func (s *Scanner) next() { - if s.rdOffset < len(s.src) { - s.offset = s.rdOffset - if s.ch == '\n' { - s.lineOffset = s.offset - s.file.AddLine(s.offset) - } - r, w := rune(s.src[s.rdOffset]), 1 - switch { - case r == 0: - s.error(s.offset, "illegal character NUL") - case r >= utf8.RuneSelf: - // not ASCII - r, w = utf8.DecodeRune(s.src[s.rdOffset:]) - if r == utf8.RuneError && w == 1 { - s.error(s.offset, "illegal UTF-8 encoding") - } else if r == bom && s.offset > 0 { - s.error(s.offset, "illegal byte order mark") - } - } - s.rdOffset += w - s.ch = r - } else { - s.offset = len(s.src) - if s.ch == '\n' { - s.lineOffset = s.offset - s.file.AddLine(s.offset) - } - s.ch = eof - } -} - -// peek returns the byte following the most recently read character without -// advancing the scanner. If the scanner is at EOF, peek returns 0. -func (s *Scanner) peek() byte { - if s.rdOffset < len(s.src) { - return s.src[s.rdOffset] - } - return 0 -} - -// A mode value is a set of flags (or 0). -// They control scanner behavior. -type Mode uint - -const ( - ScanComments Mode = 1 << iota // return comments as COMMENT tokens - dontInsertSemis // do not automatically insert semicolons - for testing only -) - -// Init prepares the scanner s to tokenize the text src by setting the -// scanner at the beginning of src. The scanner uses the file set file -// for position information and it adds line information for each line. -// It is ok to re-use the same file when re-scanning the same file as -// line information which is already present is ignored. Init causes a -// panic if the file size does not match the src size. -// -// Calls to Scan will invoke the error handler err if they encounter a -// syntax error and err is not nil. Also, for each error encountered, -// the Scanner field ErrorCount is incremented by one. The mode parameter -// determines how comments are handled. -// -// Note that Init may call err if there is an error in the first character -// of the file. -func (s *Scanner) Init(file *token.File, src []byte, err ErrorHandler, mode Mode) { - // Explicitly initialize all fields since a scanner may be reused. - if file.Size() != len(src) { - panic(fmt.Sprintf("file size (%d) does not match src len (%d)", file.Size(), len(src))) - } - s.file = file - s.dir, _ = filepath.Split(file.Name()) - s.src = src - s.err = err - s.mode = mode - - s.ch = ' ' - s.offset = 0 - s.rdOffset = 0 - s.lineOffset = 0 - s.insertSemi = false - s.ErrorCount = 0 - - s.next() - if s.ch == bom { - s.next() // ignore BOM at file beginning - } -} - -func (s *Scanner) error(offs int, msg string) { - if s.err != nil { - s.err(s.file.Position(s.file.Pos(offs)), msg) - } - s.ErrorCount++ -} - -func (s *Scanner) errorf(offs int, format string, args ...interface{}) { - s.error(offs, fmt.Sprintf(format, args...)) -} - -func (s *Scanner) scanComment() string { - // initial '/' already consumed; s.ch == '/' || s.ch == '*' - offs := s.offset - 1 // position of initial '/' - next := -1 // position immediately following the comment; < 0 means invalid comment - numCR := 0 - - if s.ch == '/' { - //-style comment - // (the final '\n' is not considered part of the comment) - s.next() - for s.ch != '\n' && s.ch >= 0 { - if s.ch == '\r' { - numCR++ - } - s.next() - } - // if we are at '\n', the position following the comment is afterwards - next = s.offset - if s.ch == '\n' { - next++ - } - goto exit - } - - /*-style comment */ - s.next() - for s.ch >= 0 { - ch := s.ch - if ch == '\r' { - numCR++ - } - s.next() - if ch == '*' && s.ch == '/' { - s.next() - next = s.offset - goto exit - } - } - - s.error(offs, "comment not terminated") - -exit: - lit := s.src[offs:s.offset] - - // On Windows, a (//-comment) line may end in "\r\n". - // Remove the final '\r' before analyzing the text for - // line directives (matching the compiler). Remove any - // other '\r' afterwards (matching the pre-existing be- - // havior of the scanner). - if numCR > 0 && len(lit) >= 2 && lit[1] == '/' && lit[len(lit)-1] == '\r' { - lit = lit[:len(lit)-1] - numCR-- - } - - // interpret line directives - // (//line directives must start at the beginning of the current line) - if next >= 0 /* implies valid comment */ && (lit[1] == '*' || offs == s.lineOffset) && bytes.HasPrefix(lit[2:], prefix) { - s.updateLineInfo(next, offs, lit) - } - - if numCR > 0 { - lit = stripCR(lit, lit[1] == '*') - } - - return string(lit) -} - -var prefix = []byte("line ") - -// updateLineInfo parses the incoming comment text at offset offs -// as a line directive. If successful, it updates the line info table -// for the position next per the line directive. -func (s *Scanner) updateLineInfo(next, offs int, text []byte) { - // extract comment text - if text[1] == '*' { - text = text[:len(text)-2] // lop off trailing "*/" - } - text = text[7:] // lop off leading "//line " or "/*line " - offs += 7 - - i, n, ok := trailingDigits(text) - if i == 0 { - return // ignore (not a line directive) - } - // i > 0 - - if !ok { - // text has a suffix :xxx but xxx is not a number - s.error(offs+i, "invalid line number: "+string(text[i:])) - return - } - - var line, col int - i2, n2, ok2 := trailingDigits(text[:i-1]) - if ok2 { - //line filename:line:col - i, i2 = i2, i - line, col = n2, n - if col == 0 { - s.error(offs+i2, "invalid column number: "+string(text[i2:])) - return - } - text = text[:i2-1] // lop off ":col" - } else { - //line filename:line - line = n - } - - if line == 0 { - s.error(offs+i, "invalid line number: "+string(text[i:])) - return - } - - // If we have a column (//line filename:line:col form), - // an empty filename means to use the previous filename. - filename := string(text[:i-1]) // lop off ":line", and trim white space - if filename == "" && ok2 { - filename = s.file.Position(s.file.Pos(offs)).Filename - } else if filename != "" { - // Put a relative filename in the current directory. - // This is for compatibility with earlier releases. - // See issue 26671. - filename = filepath.Clean(filename) - if !filepath.IsAbs(filename) { - filename = filepath.Join(s.dir, filename) - } - } - - s.file.AddLineColumnInfo(next, filename, line, col) -} - -func trailingDigits(text []byte) (int, int, bool) { - i := bytes.LastIndexByte(text, ':') // look from right (Windows filenames may contain ':') - if i < 0 { - return 0, 0, false // no ":" - } - // i >= 0 - n, err := strconv.ParseUint(string(text[i+1:]), 10, 0) - return i + 1, int(n), err == nil -} - -func (s *Scanner) findLineEnd() bool { - // initial '/' already consumed - - defer func(offs int) { - // reset scanner state to where it was upon calling findLineEnd - s.ch = '/' - s.offset = offs - s.rdOffset = offs + 1 - s.next() // consume initial '/' again - }(s.offset - 1) - - // read ahead until a newline, EOF, or non-comment token is found - for s.ch == '/' || s.ch == '*' { - if s.ch == '/' { - //-style comment always contains a newline - return true - } - /*-style comment: look for newline */ - s.next() - for s.ch >= 0 { - ch := s.ch - if ch == '\n' { - return true - } - s.next() - if ch == '*' && s.ch == '/' { - s.next() - break - } - } - s.skipWhitespace() // s.insertSemi is set - if s.ch < 0 || s.ch == '\n' { - return true - } - if s.ch != '/' { - // non-comment token - return false - } - s.next() // consume '/' - } - - return false -} - -func isLetter(ch rune) bool { - return 'a' <= lower(ch) && lower(ch) <= 'z' || ch == '_' || ch >= utf8.RuneSelf && unicode.IsLetter(ch) -} - -func isDigit(ch rune) bool { - return isDecimal(ch) || ch >= utf8.RuneSelf && unicode.IsDigit(ch) -} - -// scanIdentifier reads the string of valid identifier characters at s.offset. -// It must only be called when s.ch is known to be a valid letter. -// -// Be careful when making changes to this function: it is optimized and affects -// scanning performance significantly. -func (s *Scanner) scanIdentifier() string { - offs := s.offset - - // Optimize for the common case of an ASCII identifier. - // - // Ranging over s.src[s.rdOffset:] lets us avoid some bounds checks, and - // avoids conversions to runes. - // - // In case we encounter a non-ASCII character, fall back on the slower path - // of calling into s.next(). - for rdOffset, b := range s.src[s.rdOffset:] { - if 'a' <= b && b <= 'z' || 'A' <= b && b <= 'Z' || b == '_' || '0' <= b && b <= '9' { - // Avoid assigning a rune for the common case of an ascii character. - continue - } - s.rdOffset += rdOffset - if 0 < b && b < utf8.RuneSelf { - // Optimization: we've encountered an ASCII character that's not a letter - // or number. Avoid the call into s.next() and corresponding set up. - // - // Note that s.next() does some line accounting if s.ch is '\n', so this - // shortcut is only possible because we know that the preceding character - // is not '\n'. - s.ch = rune(b) - s.offset = s.rdOffset - s.rdOffset++ - goto exit - } - // We know that the preceding character is valid for an identifier because - // scanIdentifier is only called when s.ch is a letter, so calling s.next() - // at s.rdOffset resets the scanner state. - s.next() - for isLetter(s.ch) || isDigit(s.ch) { - s.next() - } - goto exit - } - s.offset = len(s.src) - s.rdOffset = len(s.src) - s.ch = eof - -exit: - return string(s.src[offs:s.offset]) -} - -func digitVal(ch rune) int { - switch { - case '0' <= ch && ch <= '9': - return int(ch - '0') - case 'a' <= lower(ch) && lower(ch) <= 'f': - return int(lower(ch) - 'a' + 10) - } - return 16 // larger than any legal digit val -} - -func lower(ch rune) rune { return ('a' - 'A') | ch } // returns lower-case ch iff ch is ASCII letter -func isDecimal(ch rune) bool { return '0' <= ch && ch <= '9' } -func isHex(ch rune) bool { return '0' <= ch && ch <= '9' || 'a' <= lower(ch) && lower(ch) <= 'f' } - -// digits accepts the sequence { digit | '_' }. -// If base <= 10, digits accepts any decimal digit but records -// the offset (relative to the source start) of a digit >= base -// in *invalid, if *invalid < 0. -// digits returns a bitset describing whether the sequence contained -// digits (bit 0 is set), or separators '_' (bit 1 is set). -func (s *Scanner) digits(base int, invalid *int) (digsep int) { - if base <= 10 { - max := rune('0' + base) - for isDecimal(s.ch) || s.ch == '_' { - ds := 1 - if s.ch == '_' { - ds = 2 - } else if s.ch >= max && *invalid < 0 { - *invalid = s.offset // record invalid rune offset - } - digsep |= ds - s.next() - } - } else { - for isHex(s.ch) || s.ch == '_' { - ds := 1 - if s.ch == '_' { - ds = 2 - } - digsep |= ds - s.next() - } - } - return -} - -func (s *Scanner) scanNumber() (token.Token, string) { - offs := s.offset - tok := token.ILLEGAL - - base := 10 // number base - prefix := rune(0) // one of 0 (decimal), '0' (0-octal), 'x', 'o', or 'b' - digsep := 0 // bit 0: digit present, bit 1: '_' present - invalid := -1 // index of invalid digit in literal, or < 0 - - // integer part - if s.ch != '.' { - tok = token.INT - if s.ch == '0' { - s.next() - switch lower(s.ch) { - case 'x': - s.next() - base, prefix = 16, 'x' - case 'o': - s.next() - base, prefix = 8, 'o' - case 'b': - s.next() - base, prefix = 2, 'b' - default: - base, prefix = 8, '0' - digsep = 1 // leading 0 - } - } - digsep |= s.digits(base, &invalid) - } - - // fractional part - if s.ch == '.' { - tok = token.FLOAT - if prefix == 'o' || prefix == 'b' { - s.error(s.offset, "invalid radix point in "+litname(prefix)) - } - s.next() - digsep |= s.digits(base, &invalid) - } - - if digsep&1 == 0 { - s.error(s.offset, litname(prefix)+" has no digits") - } - - // exponent - if e := lower(s.ch); e == 'e' || e == 'p' { - switch { - case e == 'e' && prefix != 0 && prefix != '0': - s.errorf(s.offset, "%q exponent requires decimal mantissa", s.ch) - case e == 'p' && prefix != 'x': - s.errorf(s.offset, "%q exponent requires hexadecimal mantissa", s.ch) - } - s.next() - tok = token.FLOAT - if s.ch == '+' || s.ch == '-' { - s.next() - } - ds := s.digits(10, nil) - digsep |= ds - if ds&1 == 0 { - s.error(s.offset, "exponent has no digits") - } - } else if prefix == 'x' && tok == token.FLOAT { - s.error(s.offset, "hexadecimal mantissa requires a 'p' exponent") - } - - // suffix 'i' - if s.ch == 'i' { - tok = token.IMAG - s.next() - } - - lit := string(s.src[offs:s.offset]) - if tok == token.INT && invalid >= 0 { - s.errorf(invalid, "invalid digit %q in %s", lit[invalid-offs], litname(prefix)) - } - if digsep&2 != 0 { - if i := invalidSep(lit); i >= 0 { - s.error(offs+i, "'_' must separate successive digits") - } - } - - return tok, lit -} - -func litname(prefix rune) string { - switch prefix { - case 'x': - return "hexadecimal literal" - case 'o', '0': - return "octal literal" - case 'b': - return "binary literal" - } - return "decimal literal" -} - -// invalidSep returns the index of the first invalid separator in x, or -1. -func invalidSep(x string) int { - x1 := ' ' // prefix char, we only care if it's 'x' - d := '.' // digit, one of '_', '0' (a digit), or '.' (anything else) - i := 0 - - // a prefix counts as a digit - if len(x) >= 2 && x[0] == '0' { - x1 = lower(rune(x[1])) - if x1 == 'x' || x1 == 'o' || x1 == 'b' { - d = '0' - i = 2 - } - } - - // mantissa and exponent - for ; i < len(x); i++ { - p := d // previous digit - d = rune(x[i]) - switch { - case d == '_': - if p != '0' { - return i - } - case isDecimal(d) || x1 == 'x' && isHex(d): - d = '0' - default: - if p == '_' { - return i - 1 - } - d = '.' - } - } - if d == '_' { - return len(x) - 1 - } - - return -1 -} - -// scanEscape parses an escape sequence where rune is the accepted -// escaped quote. In case of a syntax error, it stops at the offending -// character (without consuming it) and returns false. Otherwise -// it returns true. -func (s *Scanner) scanEscape(quote rune) bool { - offs := s.offset - - var n int - var base, max uint32 - switch s.ch { - case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', quote: - s.next() - return true - case '0', '1', '2', '3', '4', '5', '6', '7': - n, base, max = 3, 8, 255 - case 'x': - s.next() - n, base, max = 2, 16, 255 - case 'u': - s.next() - n, base, max = 4, 16, unicode.MaxRune - case 'U': - s.next() - n, base, max = 8, 16, unicode.MaxRune - default: - msg := "unknown escape sequence" - if s.ch < 0 { - msg = "escape sequence not terminated" - } - s.error(offs, msg) - return false - } - - var x uint32 - for n > 0 { - d := uint32(digitVal(s.ch)) - if d >= base { - msg := fmt.Sprintf("illegal character %#U in escape sequence", s.ch) - if s.ch < 0 { - msg = "escape sequence not terminated" - } - s.error(s.offset, msg) - return false - } - x = x*base + d - s.next() - n-- - } - - if x > max || 0xD800 <= x && x < 0xE000 { - s.error(offs, "escape sequence is invalid Unicode code point") - return false - } - - return true -} - -func (s *Scanner) scanRune() string { - // '\'' opening already consumed - offs := s.offset - 1 - - valid := true - n := 0 - for { - ch := s.ch - if ch == '\n' || ch < 0 { - // only report error if we don't have one already - if valid { - s.error(offs, "rune literal not terminated") - valid = false - } - break - } - s.next() - if ch == '\'' { - break - } - n++ - if ch == '\\' { - if !s.scanEscape('\'') { - valid = false - } - // continue to read to closing quote - } - } - - if valid && n != 1 { - s.error(offs, "illegal rune literal") - } - - return string(s.src[offs:s.offset]) -} - -func (s *Scanner) scanString() string { - // '"' opening already consumed - offs := s.offset - 1 - - for { - ch := s.ch - if ch == '\n' || ch < 0 { - s.error(offs, "string literal not terminated") - break - } - s.next() - if ch == '"' { - break - } - if ch == '\\' { - s.scanEscape('"') - } - } - - return string(s.src[offs:s.offset]) -} - -func stripCR(b []byte, comment bool) []byte { - c := make([]byte, len(b)) - i := 0 - for j, ch := range b { - // In a /*-style comment, don't strip \r from *\r/ (incl. - // sequences of \r from *\r\r...\r/) since the resulting - // */ would terminate the comment too early unless the \r - // is immediately following the opening /* in which case - // it's ok because /*/ is not closed yet (issue #11151). - if ch != '\r' || comment && i > len("/*") && c[i-1] == '*' && j+1 < len(b) && b[j+1] == '/' { - c[i] = ch - i++ - } - } - return c[:i] -} - -func (s *Scanner) scanRawString() string { - // '`' opening already consumed - offs := s.offset - 1 - - hasCR := false - for { - ch := s.ch - if ch < 0 { - s.error(offs, "raw string literal not terminated") - break - } - s.next() - if ch == '`' { - break - } - if ch == '\r' { - hasCR = true - } - } - - lit := s.src[offs:s.offset] - if hasCR { - lit = stripCR(lit, false) - } - - return string(lit) -} - -func (s *Scanner) skipWhitespace() { - for s.ch == ' ' || s.ch == '\t' || s.ch == '\n' && !s.insertSemi || s.ch == '\r' { - s.next() - } -} - -// Helper functions for scanning multi-byte tokens such as >> += >>= . -// Different routines recognize different length tok_i based on matches -// of ch_i. If a token ends in '=', the result is tok1 or tok3 -// respectively. Otherwise, the result is tok0 if there was no other -// matching character, or tok2 if the matching character was ch2. - -func (s *Scanner) switch2(tok0, tok1 token.Token) token.Token { - if s.ch == '=' { - s.next() - return tok1 - } - return tok0 -} - -func (s *Scanner) switch3(tok0, tok1 token.Token, ch2 rune, tok2 token.Token) token.Token { - if s.ch == '=' { - s.next() - return tok1 - } - if s.ch == ch2 { - s.next() - return tok2 - } - return tok0 -} - -func (s *Scanner) switch4(tok0, tok1 token.Token, ch2 rune, tok2, tok3 token.Token) token.Token { - if s.ch == '=' { - s.next() - return tok1 - } - if s.ch == ch2 { - s.next() - if s.ch == '=' { - s.next() - return tok3 - } - return tok2 - } - return tok0 -} - -// Scan scans the next token and returns the token position, the token, -// and its literal string if applicable. The source end is indicated by -// token.EOF. -// -// If the returned token is a literal (token.IDENT, token.INT, token.FLOAT, -// token.IMAG, token.CHAR, token.STRING) or token.COMMENT, the literal string -// has the corresponding value. -// -// If the returned token is a keyword, the literal string is the keyword. -// -// If the returned token is token.SEMICOLON, the corresponding -// literal string is ";" if the semicolon was present in the source, -// and "\n" if the semicolon was inserted because of a newline or -// at EOF. -// -// If the returned token is token.ILLEGAL, the literal string is the -// offending character. -// -// In all other cases, Scan returns an empty literal string. -// -// For more tolerant parsing, Scan will return a valid token if -// possible even if a syntax error was encountered. Thus, even -// if the resulting token sequence contains no illegal tokens, -// a client may not assume that no error occurred. Instead it -// must check the scanner's ErrorCount or the number of calls -// of the error handler, if there was one installed. -// -// Scan adds line information to the file added to the file -// set with Init. Token positions are relative to that file -// and thus relative to the file set. -func (s *Scanner) Scan() (pos token.Pos, tok token.Token, lit string) { -scanAgain: - s.skipWhitespace() - - // current token start - pos = s.file.Pos(s.offset) - - // determine token value - insertSemi := false - switch ch := s.ch; { - case isLetter(ch): - lit = s.scanIdentifier() - if len(lit) > 1 { - // keywords are longer than one letter - avoid lookup otherwise - tok = token.Lookup(lit) - switch tok { - case token.IDENT, token.BREAK, token.CONTINUE, token.FALLTHROUGH, token.RETURN: - insertSemi = true - } - } else { - insertSemi = true - tok = token.IDENT - } - case isDecimal(ch) || ch == '.' && isDecimal(rune(s.peek())): - insertSemi = true - tok, lit = s.scanNumber() - default: - s.next() // always make progress - switch ch { - case -1: - if s.insertSemi { - s.insertSemi = false // EOF consumed - return pos, token.SEMICOLON, "\n" - } - tok = token.EOF - case '\n': - // we only reach here if s.insertSemi was - // set in the first place and exited early - // from s.skipWhitespace() - s.insertSemi = false // newline consumed - return pos, token.SEMICOLON, "\n" - case '"': - insertSemi = true - tok = token.STRING - lit = s.scanString() - case '\'': - insertSemi = true - tok = token.CHAR - lit = s.scanRune() - case '`': - insertSemi = true - tok = token.STRING - lit = s.scanRawString() - case ':': - tok = s.switch2(token.COLON, token.DEFINE) - case '.': - // fractions starting with a '.' are handled by outer switch - tok = token.PERIOD - if s.ch == '.' && s.peek() == '.' { - s.next() - s.next() // consume last '.' - tok = token.ELLIPSIS - } - case ',': - tok = token.COMMA - case ';': - tok = token.SEMICOLON - lit = ";" - case '(': - tok = token.LPAREN - case ')': - insertSemi = true - tok = token.RPAREN - case '[': - tok = token.LBRACK - case ']': - insertSemi = true - tok = token.RBRACK - case '{': - tok = token.LBRACE - case '}': - insertSemi = true - tok = token.RBRACE - case '+': - tok = s.switch3(token.ADD, token.ADD_ASSIGN, '+', token.INC) - if tok == token.INC { - insertSemi = true - } - case '-': - tok = s.switch3(token.SUB, token.SUB_ASSIGN, '-', token.DEC) - if tok == token.DEC { - insertSemi = true - } - case '*': - tok = s.switch2(token.MUL, token.MUL_ASSIGN) - case '/': - if s.ch == '/' || s.ch == '*' { - // comment - if s.insertSemi && s.findLineEnd() { - // reset position to the beginning of the comment - s.ch = '/' - s.offset = s.file.Offset(pos) - s.rdOffset = s.offset + 1 - s.insertSemi = false // newline consumed - return pos, token.SEMICOLON, "\n" - } - comment := s.scanComment() - if s.mode&ScanComments == 0 { - // skip comment - s.insertSemi = false // newline consumed - goto scanAgain - } - tok = token.COMMENT - lit = comment - } else { - tok = s.switch2(token.QUO, token.QUO_ASSIGN) - } - case '%': - tok = s.switch2(token.REM, token.REM_ASSIGN) - case '^': - tok = s.switch2(token.XOR, token.XOR_ASSIGN) - case '<': - if s.ch == '-' { - s.next() - tok = token.ARROW - } else { - tok = s.switch4(token.LSS, token.LEQ, '<', token.SHL, token.SHL_ASSIGN) - } - case '>': - tok = s.switch4(token.GTR, token.GEQ, '>', token.SHR, token.SHR_ASSIGN) - case '=': - tok = s.switch2(token.ASSIGN, token.EQL) - case '!': - tok = s.switch2(token.NOT, token.NEQ) - case '&': - if s.ch == '^' { - s.next() - tok = s.switch2(token.AND_NOT, token.AND_NOT_ASSIGN) - } else { - tok = s.switch3(token.AND, token.AND_ASSIGN, '&', token.LAND) - } - case '|': - tok = s.switch3(token.OR, token.OR_ASSIGN, '|', token.LOR) - case '~': - tok = token.TILDE - default: - // next reports unexpected BOMs - don't repeat - if ch != bom { - s.errorf(s.file.Offset(pos), "illegal character %#U", ch) - } - insertSemi = s.insertSemi // preserve insertSemi info - tok = token.ILLEGAL - lit = string(ch) - } - } - if s.mode&dontInsertSemis == 0 { - s.insertSemi = insertSemi - } - - return -} diff --git a/internal/backport/go/scanner/scanner_test.go b/internal/backport/go/scanner/scanner_test.go deleted file mode 100644 index d56abcb9db..0000000000 --- a/internal/backport/go/scanner/scanner_test.go +++ /dev/null @@ -1,1127 +0,0 @@ -// Copyright 2009 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 scanner - -import ( - "golang.org/x/website/internal/backport/go/token" - "os" - "path/filepath" - "runtime" - "strings" - "testing" -) - -var fset = token.NewFileSet() - -const /* class */ ( - special = iota - literal - operator - keyword -) - -func tokenclass(tok token.Token) int { - switch { - case tok.IsLiteral(): - return literal - case tok.IsOperator(): - return operator - case tok.IsKeyword(): - return keyword - } - return special -} - -type elt struct { - tok token.Token - lit string - class int -} - -var tokens = []elt{ - // Special tokens - {token.COMMENT, "/* a comment */", special}, - {token.COMMENT, "// a comment \n", special}, - {token.COMMENT, "/*\r*/", special}, - {token.COMMENT, "/**\r/*/", special}, // issue 11151 - {token.COMMENT, "/**\r\r/*/", special}, - {token.COMMENT, "//\r\n", special}, - - // Identifiers and basic type literals - {token.IDENT, "foobar", literal}, - {token.IDENT, "a۰۱۸", literal}, - {token.IDENT, "foo६४", literal}, - {token.IDENT, "bar9876", literal}, - {token.IDENT, "ŝ", literal}, // was bug (issue 4000) - {token.IDENT, "ŝfoo", literal}, // was bug (issue 4000) - {token.INT, "0", literal}, - {token.INT, "1", literal}, - {token.INT, "123456789012345678890", literal}, - {token.INT, "01234567", literal}, - {token.INT, "0xcafebabe", literal}, - {token.FLOAT, "0.", literal}, - {token.FLOAT, ".0", literal}, - {token.FLOAT, "3.14159265", literal}, - {token.FLOAT, "1e0", literal}, - {token.FLOAT, "1e+100", literal}, - {token.FLOAT, "1e-100", literal}, - {token.FLOAT, "2.71828e-1000", literal}, - {token.IMAG, "0i", literal}, - {token.IMAG, "1i", literal}, - {token.IMAG, "012345678901234567889i", literal}, - {token.IMAG, "123456789012345678890i", literal}, - {token.IMAG, "0.i", literal}, - {token.IMAG, ".0i", literal}, - {token.IMAG, "3.14159265i", literal}, - {token.IMAG, "1e0i", literal}, - {token.IMAG, "1e+100i", literal}, - {token.IMAG, "1e-100i", literal}, - {token.IMAG, "2.71828e-1000i", literal}, - {token.CHAR, "'a'", literal}, - {token.CHAR, "'\\000'", literal}, - {token.CHAR, "'\\xFF'", literal}, - {token.CHAR, "'\\uff16'", literal}, - {token.CHAR, "'\\U0000ff16'", literal}, - {token.STRING, "`foobar`", literal}, - {token.STRING, "`" + `foo - bar` + - "`", - literal, - }, - {token.STRING, "`\r`", literal}, - {token.STRING, "`foo\r\nbar`", literal}, - - // Operators and delimiters - {token.ADD, "+", operator}, - {token.SUB, "-", operator}, - {token.MUL, "*", operator}, - {token.QUO, "/", operator}, - {token.REM, "%", operator}, - - {token.AND, "&", operator}, - {token.OR, "|", operator}, - {token.XOR, "^", operator}, - {token.SHL, "<<", operator}, - {token.SHR, ">>", operator}, - {token.AND_NOT, "&^", operator}, - - {token.ADD_ASSIGN, "+=", operator}, - {token.SUB_ASSIGN, "-=", operator}, - {token.MUL_ASSIGN, "*=", operator}, - {token.QUO_ASSIGN, "/=", operator}, - {token.REM_ASSIGN, "%=", operator}, - - {token.AND_ASSIGN, "&=", operator}, - {token.OR_ASSIGN, "|=", operator}, - {token.XOR_ASSIGN, "^=", operator}, - {token.SHL_ASSIGN, "<<=", operator}, - {token.SHR_ASSIGN, ">>=", operator}, - {token.AND_NOT_ASSIGN, "&^=", operator}, - - {token.LAND, "&&", operator}, - {token.LOR, "||", operator}, - {token.ARROW, "<-", operator}, - {token.INC, "++", operator}, - {token.DEC, "--", operator}, - - {token.EQL, "==", operator}, - {token.LSS, "<", operator}, - {token.GTR, ">", operator}, - {token.ASSIGN, "=", operator}, - {token.NOT, "!", operator}, - - {token.NEQ, "!=", operator}, - {token.LEQ, "<=", operator}, - {token.GEQ, ">=", operator}, - {token.DEFINE, ":=", operator}, - {token.ELLIPSIS, "...", operator}, - - {token.LPAREN, "(", operator}, - {token.LBRACK, "[", operator}, - {token.LBRACE, "{", operator}, - {token.COMMA, ",", operator}, - {token.PERIOD, ".", operator}, - - {token.RPAREN, ")", operator}, - {token.RBRACK, "]", operator}, - {token.RBRACE, "}", operator}, - {token.SEMICOLON, ";", operator}, - {token.COLON, ":", operator}, - {token.TILDE, "~", operator}, - - // Keywords - {token.BREAK, "break", keyword}, - {token.CASE, "case", keyword}, - {token.CHAN, "chan", keyword}, - {token.CONST, "const", keyword}, - {token.CONTINUE, "continue", keyword}, - - {token.DEFAULT, "default", keyword}, - {token.DEFER, "defer", keyword}, - {token.ELSE, "else", keyword}, - {token.FALLTHROUGH, "fallthrough", keyword}, - {token.FOR, "for", keyword}, - - {token.FUNC, "func", keyword}, - {token.GO, "go", keyword}, - {token.GOTO, "goto", keyword}, - {token.IF, "if", keyword}, - {token.IMPORT, "import", keyword}, - - {token.INTERFACE, "interface", keyword}, - {token.MAP, "map", keyword}, - {token.PACKAGE, "package", keyword}, - {token.RANGE, "range", keyword}, - {token.RETURN, "return", keyword}, - - {token.SELECT, "select", keyword}, - {token.STRUCT, "struct", keyword}, - {token.SWITCH, "switch", keyword}, - {token.TYPE, "type", keyword}, - {token.VAR, "var", keyword}, -} - -const whitespace = " \t \n\n\n" // to separate tokens - -var source = func() []byte { - var src []byte - for _, t := range tokens { - src = append(src, t.lit...) - src = append(src, whitespace...) - } - return src -}() - -func newlineCount(s string) int { - n := 0 - for i := 0; i < len(s); i++ { - if s[i] == '\n' { - n++ - } - } - return n -} - -func checkPos(t *testing.T, lit string, p token.Pos, expected token.Position) { - pos := fset.Position(p) - // Check cleaned filenames so that we don't have to worry about - // different os.PathSeparator values. - if pos.Filename != expected.Filename && filepath.Clean(pos.Filename) != filepath.Clean(expected.Filename) { - t.Errorf("bad filename for %q: got %s, expected %s", lit, pos.Filename, expected.Filename) - } - if pos.Offset != expected.Offset { - t.Errorf("bad position for %q: got %d, expected %d", lit, pos.Offset, expected.Offset) - } - if pos.Line != expected.Line { - t.Errorf("bad line for %q: got %d, expected %d", lit, pos.Line, expected.Line) - } - if pos.Column != expected.Column { - t.Errorf("bad column for %q: got %d, expected %d", lit, pos.Column, expected.Column) - } -} - -// Verify that calling Scan() provides the correct results. -func TestScan(t *testing.T) { - whitespace_linecount := newlineCount(whitespace) - - // error handler - eh := func(_ token.Position, msg string) { - t.Errorf("error handler called (msg = %s)", msg) - } - - // verify scan - var s Scanner - s.Init(fset.AddFile("", fset.Base(), len(source)), source, eh, ScanComments|dontInsertSemis) - - // set up expected position - epos := token.Position{ - Filename: "", - Offset: 0, - Line: 1, - Column: 1, - } - - index := 0 - for { - pos, tok, lit := s.Scan() - - // check position - if tok == token.EOF { - // correction for EOF - epos.Line = newlineCount(string(source)) - epos.Column = 2 - } - checkPos(t, lit, pos, epos) - - // check token - e := elt{token.EOF, "", special} - if index < len(tokens) { - e = tokens[index] - index++ - } - if tok != e.tok { - t.Errorf("bad token for %q: got %s, expected %s", lit, tok, e.tok) - } - - // check token class - if tokenclass(tok) != e.class { - t.Errorf("bad class for %q: got %d, expected %d", lit, tokenclass(tok), e.class) - } - - // check literal - elit := "" - switch e.tok { - case token.COMMENT: - // no CRs in comments - elit = string(stripCR([]byte(e.lit), e.lit[1] == '*')) - //-style comment literal doesn't contain newline - if elit[1] == '/' { - elit = elit[0 : len(elit)-1] - } - case token.IDENT: - elit = e.lit - case token.SEMICOLON: - elit = ";" - default: - if e.tok.IsLiteral() { - // no CRs in raw string literals - elit = e.lit - if elit[0] == '`' { - elit = string(stripCR([]byte(elit), false)) - } - } else if e.tok.IsKeyword() { - elit = e.lit - } - } - if lit != elit { - t.Errorf("bad literal for %q: got %q, expected %q", lit, lit, elit) - } - - if tok == token.EOF { - break - } - - // update position - epos.Offset += len(e.lit) + len(whitespace) - epos.Line += newlineCount(e.lit) + whitespace_linecount - - } - - if s.ErrorCount != 0 { - t.Errorf("found %d errors", s.ErrorCount) - } -} - -func TestStripCR(t *testing.T) { - for _, test := range []struct{ have, want string }{ - {"//\n", "//\n"}, - {"//\r\n", "//\n"}, - {"//\r\r\r\n", "//\n"}, - {"//\r*\r/\r\n", "//*/\n"}, - {"/**/", "/**/"}, - {"/*\r/*/", "/*/*/"}, - {"/*\r*/", "/**/"}, - {"/**\r/*/", "/**\r/*/"}, - {"/*\r/\r*\r/*/", "/*/*\r/*/"}, - {"/*\r\r\r\r*/", "/**/"}, - } { - got := string(stripCR([]byte(test.have), len(test.have) >= 2 && test.have[1] == '*')) - if got != test.want { - t.Errorf("stripCR(%q) = %q; want %q", test.have, got, test.want) - } - } -} - -func checkSemi(t *testing.T, line string, mode Mode) { - var S Scanner - file := fset.AddFile("TestSemis", fset.Base(), len(line)) - S.Init(file, []byte(line), nil, mode) - pos, tok, lit := S.Scan() - for tok != token.EOF { - if tok == token.ILLEGAL { - // the illegal token literal indicates what - // kind of semicolon literal to expect - semiLit := "\n" - if lit[0] == '#' { - semiLit = ";" - } - // next token must be a semicolon - semiPos := file.Position(pos) - semiPos.Offset++ - semiPos.Column++ - pos, tok, lit = S.Scan() - if tok == token.SEMICOLON { - if lit != semiLit { - t.Errorf(`bad literal for %q: got %q, expected %q`, line, lit, semiLit) - } - checkPos(t, line, pos, semiPos) - } else { - t.Errorf("bad token for %q: got %s, expected ;", line, tok) - } - } else if tok == token.SEMICOLON { - t.Errorf("bad token for %q: got ;, expected no ;", line) - } - pos, tok, lit = S.Scan() - } -} - -var lines = []string{ - // # indicates a semicolon present in the source - // $ indicates an automatically inserted semicolon - "", - "\ufeff#;", // first BOM is ignored - "#;", - "foo$\n", - "123$\n", - "1.2$\n", - "'x'$\n", - `"x"` + "$\n", - "`x`$\n", - - "+\n", - "-\n", - "*\n", - "/\n", - "%\n", - - "&\n", - "|\n", - "^\n", - "<<\n", - ">>\n", - "&^\n", - - "+=\n", - "-=\n", - "*=\n", - "/=\n", - "%=\n", - - "&=\n", - "|=\n", - "^=\n", - "<<=\n", - ">>=\n", - "&^=\n", - - "&&\n", - "||\n", - "<-\n", - "++$\n", - "--$\n", - - "==\n", - "<\n", - ">\n", - "=\n", - "!\n", - - "!=\n", - "<=\n", - ">=\n", - ":=\n", - "...\n", - - "(\n", - "[\n", - "{\n", - ",\n", - ".\n", - - ")$\n", - "]$\n", - "}$\n", - "#;\n", - ":\n", - - "break$\n", - "case\n", - "chan\n", - "const\n", - "continue$\n", - - "default\n", - "defer\n", - "else\n", - "fallthrough$\n", - "for\n", - - "func\n", - "go\n", - "goto\n", - "if\n", - "import\n", - - "interface\n", - "map\n", - "package\n", - "range\n", - "return$\n", - - "select\n", - "struct\n", - "switch\n", - "type\n", - "var\n", - - "foo$//comment\n", - "foo$//comment", - "foo$/*comment*/\n", - "foo$/*\n*/", - "foo$/*comment*/ \n", - "foo$/*\n*/ ", - - "foo $// comment\n", - "foo $// comment", - "foo $/*comment*/\n", - "foo $/*\n*/", - "foo $/* */ /* \n */ bar$/**/\n", - "foo $/*0*/ /*1*/ /*2*/\n", - - "foo $/*comment*/ \n", - "foo $/*0*/ /*1*/ /*2*/ \n", - "foo $/**/ /*-------------*/ /*----\n*/bar $/* \n*/baa$\n", - "foo $/* an EOF terminates a line */", - "foo $/* an EOF terminates a line */ /*", - "foo $/* an EOF terminates a line */ //", - - "package main$\n\nfunc main() {\n\tif {\n\t\treturn /* */ }$\n}$\n", - "package main$", -} - -func TestSemis(t *testing.T) { - for _, line := range lines { - checkSemi(t, line, 0) - checkSemi(t, line, ScanComments) - - // if the input ended in newlines, the input must tokenize the - // same with or without those newlines - for i := len(line) - 1; i >= 0 && line[i] == '\n'; i-- { - checkSemi(t, line[0:i], 0) - checkSemi(t, line[0:i], ScanComments) - } - } -} - -type segment struct { - srcline string // a line of source text - filename string // filename for current token; error message for invalid line directives - line, column int // line and column for current token; error position for invalid line directives -} - -var segments = []segment{ - // exactly one token per line since the test consumes one token per segment - {" line1", "TestLineDirectives", 1, 3}, - {"\nline2", "TestLineDirectives", 2, 1}, - {"\nline3 //line File1.go:100", "TestLineDirectives", 3, 1}, // bad line comment, ignored - {"\nline4", "TestLineDirectives", 4, 1}, - {"\n//line File1.go:100\n line100", "File1.go", 100, 0}, - {"\n//line \t :42\n line1", " \t ", 42, 0}, - {"\n//line File2.go:200\n line200", "File2.go", 200, 0}, - {"\n//line foo\t:42\n line42", "foo\t", 42, 0}, - {"\n //line foo:42\n line43", "foo\t", 44, 0}, // bad line comment, ignored (use existing, prior filename) - {"\n//line foo 42\n line44", "foo\t", 46, 0}, // bad line comment, ignored (use existing, prior filename) - {"\n//line /bar:42\n line45", "/bar", 42, 0}, - {"\n//line ./foo:42\n line46", "foo", 42, 0}, - {"\n//line a/b/c/File1.go:100\n line100", "a/b/c/File1.go", 100, 0}, - {"\n//line c:\\bar:42\n line200", "c:\\bar", 42, 0}, - {"\n//line c:\\dir\\File1.go:100\n line201", "c:\\dir\\File1.go", 100, 0}, - - // tests for new line directive syntax - {"\n//line :100\na1", "", 100, 0}, // missing filename means empty filename - {"\n//line bar:100\nb1", "bar", 100, 0}, - {"\n//line :100:10\nc1", "bar", 100, 10}, // missing filename means current filename - {"\n//line foo:100:10\nd1", "foo", 100, 10}, - - {"\n/*line :100*/a2", "", 100, 0}, // missing filename means empty filename - {"\n/*line bar:100*/b2", "bar", 100, 0}, - {"\n/*line :100:10*/c2", "bar", 100, 10}, // missing filename means current filename - {"\n/*line foo:100:10*/d2", "foo", 100, 10}, - {"\n/*line foo:100:10*/ e2", "foo", 100, 14}, // line-directive relative column - {"\n/*line foo:100:10*/\n\nf2", "foo", 102, 1}, // absolute column since on new line -} - -var dirsegments = []segment{ - // exactly one token per line since the test consumes one token per segment - {" line1", "TestLineDir/TestLineDirectives", 1, 3}, - {"\n//line File1.go:100\n line100", "TestLineDir/File1.go", 100, 0}, -} - -var dirUnixSegments = []segment{ - {"\n//line /bar:42\n line42", "/bar", 42, 0}, -} - -var dirWindowsSegments = []segment{ - {"\n//line c:\\bar:42\n line42", "c:\\bar", 42, 0}, -} - -// Verify that line directives are interpreted correctly. -func TestLineDirectives(t *testing.T) { - testSegments(t, segments, "TestLineDirectives") - testSegments(t, dirsegments, "TestLineDir/TestLineDirectives") - if runtime.GOOS == "windows" { - testSegments(t, dirWindowsSegments, "TestLineDir/TestLineDirectives") - } else { - testSegments(t, dirUnixSegments, "TestLineDir/TestLineDirectives") - } -} - -func testSegments(t *testing.T, segments []segment, filename string) { - var src string - for _, e := range segments { - src += e.srcline - } - - // verify scan - var S Scanner - file := fset.AddFile(filename, fset.Base(), len(src)) - S.Init(file, []byte(src), func(pos token.Position, msg string) { t.Error(Error{pos, msg}) }, dontInsertSemis) - for _, s := range segments { - p, _, lit := S.Scan() - pos := file.Position(p) - checkPos(t, lit, p, token.Position{ - Filename: s.filename, - Offset: pos.Offset, - Line: s.line, - Column: s.column, - }) - } - - if S.ErrorCount != 0 { - t.Errorf("got %d errors", S.ErrorCount) - } -} - -// The filename is used for the error message in these test cases. -// The first line directive is valid and used to control the expected error line. -var invalidSegments = []segment{ - {"\n//line :1:1\n//line foo:42 extra text\ndummy", "invalid line number: 42 extra text", 1, 12}, - {"\n//line :2:1\n//line foobar:\ndummy", "invalid line number: ", 2, 15}, - {"\n//line :5:1\n//line :0\ndummy", "invalid line number: 0", 5, 9}, - {"\n//line :10:1\n//line :1:0\ndummy", "invalid column number: 0", 10, 11}, - {"\n//line :1:1\n//line :foo:0\ndummy", "invalid line number: 0", 1, 13}, // foo is considered part of the filename -} - -// Verify that invalid line directives get the correct error message. -func TestInvalidLineDirectives(t *testing.T) { - // make source - var src string - for _, e := range invalidSegments { - src += e.srcline - } - - // verify scan - var S Scanner - var s segment // current segment - file := fset.AddFile(filepath.Join("dir", "TestInvalidLineDirectives"), fset.Base(), len(src)) - S.Init(file, []byte(src), func(pos token.Position, msg string) { - if msg != s.filename { - t.Errorf("got error %q; want %q", msg, s.filename) - } - if pos.Line != s.line || pos.Column != s.column { - t.Errorf("got position %d:%d; want %d:%d", pos.Line, pos.Column, s.line, s.column) - } - }, dontInsertSemis) - for _, s = range invalidSegments { - S.Scan() - } - - if S.ErrorCount != len(invalidSegments) { - t.Errorf("got %d errors; want %d", S.ErrorCount, len(invalidSegments)) - } -} - -// Verify that initializing the same scanner more than once works correctly. -func TestInit(t *testing.T) { - var s Scanner - - // 1st init - src1 := "if true { }" - f1 := fset.AddFile("src1", fset.Base(), len(src1)) - s.Init(f1, []byte(src1), nil, dontInsertSemis) - if f1.Size() != len(src1) { - t.Errorf("bad file size: got %d, expected %d", f1.Size(), len(src1)) - } - s.Scan() // if - s.Scan() // true - _, tok, _ := s.Scan() // { - if tok != token.LBRACE { - t.Errorf("bad token: got %s, expected %s", tok, token.LBRACE) - } - - // 2nd init - src2 := "go true { ]" - f2 := fset.AddFile("src2", fset.Base(), len(src2)) - s.Init(f2, []byte(src2), nil, dontInsertSemis) - if f2.Size() != len(src2) { - t.Errorf("bad file size: got %d, expected %d", f2.Size(), len(src2)) - } - _, tok, _ = s.Scan() // go - if tok != token.GO { - t.Errorf("bad token: got %s, expected %s", tok, token.GO) - } - - if s.ErrorCount != 0 { - t.Errorf("found %d errors", s.ErrorCount) - } -} - -func TestStdErrorHander(t *testing.T) { - const src = "@\n" + // illegal character, cause an error - "@ @\n" + // two errors on the same line - "//line File2:20\n" + - "@\n" + // different file, but same line - "//line File2:1\n" + - "@ @\n" + // same file, decreasing line number - "//line File1:1\n" + - "@ @ @" // original file, line 1 again - - var list ErrorList - eh := func(pos token.Position, msg string) { list.Add(pos, msg) } - - var s Scanner - s.Init(fset.AddFile("File1", fset.Base(), len(src)), []byte(src), eh, dontInsertSemis) - for { - if _, tok, _ := s.Scan(); tok == token.EOF { - break - } - } - - if len(list) != s.ErrorCount { - t.Errorf("found %d errors, expected %d", len(list), s.ErrorCount) - } - - if len(list) != 9 { - t.Errorf("found %d raw errors, expected 9", len(list)) - PrintError(os.Stderr, list) - } - - list.Sort() - if len(list) != 9 { - t.Errorf("found %d sorted errors, expected 9", len(list)) - PrintError(os.Stderr, list) - } - - list.RemoveMultiples() - if len(list) != 4 { - t.Errorf("found %d one-per-line errors, expected 4", len(list)) - PrintError(os.Stderr, list) - } -} - -type errorCollector struct { - cnt int // number of errors encountered - msg string // last error message encountered - pos token.Position // last error position encountered -} - -func checkError(t *testing.T, src string, tok token.Token, pos int, lit, err string) { - var s Scanner - var h errorCollector - eh := func(pos token.Position, msg string) { - h.cnt++ - h.msg = msg - h.pos = pos - } - s.Init(fset.AddFile("", fset.Base(), len(src)), []byte(src), eh, ScanComments|dontInsertSemis) - _, tok0, lit0 := s.Scan() - if tok0 != tok { - t.Errorf("%q: got %s, expected %s", src, tok0, tok) - } - if tok0 != token.ILLEGAL && lit0 != lit { - t.Errorf("%q: got literal %q, expected %q", src, lit0, lit) - } - cnt := 0 - if err != "" { - cnt = 1 - } - if h.cnt != cnt { - t.Errorf("%q: got cnt %d, expected %d", src, h.cnt, cnt) - } - if h.msg != err { - t.Errorf("%q: got msg %q, expected %q", src, h.msg, err) - } - if h.pos.Offset != pos { - t.Errorf("%q: got offset %d, expected %d", src, h.pos.Offset, pos) - } -} - -var errors = []struct { - src string - tok token.Token - pos int - lit string - err string -}{ - {"\a", token.ILLEGAL, 0, "", "illegal character U+0007"}, - {`#`, token.ILLEGAL, 0, "", "illegal character U+0023 '#'"}, - {`…`, token.ILLEGAL, 0, "", "illegal character U+2026 '…'"}, - {"..", token.PERIOD, 0, "", ""}, // two periods, not invalid token (issue #28112) - {`' '`, token.CHAR, 0, `' '`, ""}, - {`''`, token.CHAR, 0, `''`, "illegal rune literal"}, - {`'12'`, token.CHAR, 0, `'12'`, "illegal rune literal"}, - {`'123'`, token.CHAR, 0, `'123'`, "illegal rune literal"}, - {`'\0'`, token.CHAR, 3, `'\0'`, "illegal character U+0027 ''' in escape sequence"}, - {`'\07'`, token.CHAR, 4, `'\07'`, "illegal character U+0027 ''' in escape sequence"}, - {`'\8'`, token.CHAR, 2, `'\8'`, "unknown escape sequence"}, - {`'\08'`, token.CHAR, 3, `'\08'`, "illegal character U+0038 '8' in escape sequence"}, - {`'\x'`, token.CHAR, 3, `'\x'`, "illegal character U+0027 ''' in escape sequence"}, - {`'\x0'`, token.CHAR, 4, `'\x0'`, "illegal character U+0027 ''' in escape sequence"}, - {`'\x0g'`, token.CHAR, 4, `'\x0g'`, "illegal character U+0067 'g' in escape sequence"}, - {`'\u'`, token.CHAR, 3, `'\u'`, "illegal character U+0027 ''' in escape sequence"}, - {`'\u0'`, token.CHAR, 4, `'\u0'`, "illegal character U+0027 ''' in escape sequence"}, - {`'\u00'`, token.CHAR, 5, `'\u00'`, "illegal character U+0027 ''' in escape sequence"}, - {`'\u000'`, token.CHAR, 6, `'\u000'`, "illegal character U+0027 ''' in escape sequence"}, - {`'\u000`, token.CHAR, 6, `'\u000`, "escape sequence not terminated"}, - {`'\u0000'`, token.CHAR, 0, `'\u0000'`, ""}, - {`'\U'`, token.CHAR, 3, `'\U'`, "illegal character U+0027 ''' in escape sequence"}, - {`'\U0'`, token.CHAR, 4, `'\U0'`, "illegal character U+0027 ''' in escape sequence"}, - {`'\U00'`, token.CHAR, 5, `'\U00'`, "illegal character U+0027 ''' in escape sequence"}, - {`'\U000'`, token.CHAR, 6, `'\U000'`, "illegal character U+0027 ''' in escape sequence"}, - {`'\U0000'`, token.CHAR, 7, `'\U0000'`, "illegal character U+0027 ''' in escape sequence"}, - {`'\U00000'`, token.CHAR, 8, `'\U00000'`, "illegal character U+0027 ''' in escape sequence"}, - {`'\U000000'`, token.CHAR, 9, `'\U000000'`, "illegal character U+0027 ''' in escape sequence"}, - {`'\U0000000'`, token.CHAR, 10, `'\U0000000'`, "illegal character U+0027 ''' in escape sequence"}, - {`'\U0000000`, token.CHAR, 10, `'\U0000000`, "escape sequence not terminated"}, - {`'\U00000000'`, token.CHAR, 0, `'\U00000000'`, ""}, - {`'\Uffffffff'`, token.CHAR, 2, `'\Uffffffff'`, "escape sequence is invalid Unicode code point"}, - {`'`, token.CHAR, 0, `'`, "rune literal not terminated"}, - {`'\`, token.CHAR, 2, `'\`, "escape sequence not terminated"}, - {"'\n", token.CHAR, 0, "'", "rune literal not terminated"}, - {"'\n ", token.CHAR, 0, "'", "rune literal not terminated"}, - {`""`, token.STRING, 0, `""`, ""}, - {`"abc`, token.STRING, 0, `"abc`, "string literal not terminated"}, - {"\"abc\n", token.STRING, 0, `"abc`, "string literal not terminated"}, - {"\"abc\n ", token.STRING, 0, `"abc`, "string literal not terminated"}, - {"``", token.STRING, 0, "``", ""}, - {"`", token.STRING, 0, "`", "raw string literal not terminated"}, - {"/**/", token.COMMENT, 0, "/**/", ""}, - {"/*", token.COMMENT, 0, "/*", "comment not terminated"}, - {"077", token.INT, 0, "077", ""}, - {"078.", token.FLOAT, 0, "078.", ""}, - {"07801234567.", token.FLOAT, 0, "07801234567.", ""}, - {"078e0", token.FLOAT, 0, "078e0", ""}, - {"0E", token.FLOAT, 2, "0E", "exponent has no digits"}, // issue 17621 - {"078", token.INT, 2, "078", "invalid digit '8' in octal literal"}, - {"07090000008", token.INT, 3, "07090000008", "invalid digit '9' in octal literal"}, - {"0x", token.INT, 2, "0x", "hexadecimal literal has no digits"}, - {"\"abc\x00def\"", token.STRING, 4, "\"abc\x00def\"", "illegal character NUL"}, - {"\"abc\x80def\"", token.STRING, 4, "\"abc\x80def\"", "illegal UTF-8 encoding"}, - {"\ufeff\ufeff", token.ILLEGAL, 3, "\ufeff\ufeff", "illegal byte order mark"}, // only first BOM is ignored - {"//\ufeff", token.COMMENT, 2, "//\ufeff", "illegal byte order mark"}, // only first BOM is ignored - {"'\ufeff" + `'`, token.CHAR, 1, "'\ufeff" + `'`, "illegal byte order mark"}, // only first BOM is ignored - {`"` + "abc\ufeffdef" + `"`, token.STRING, 4, `"` + "abc\ufeffdef" + `"`, "illegal byte order mark"}, // only first BOM is ignored - {"abc\x00def", token.IDENT, 3, "abc", "illegal character NUL"}, - {"abc\x00", token.IDENT, 3, "abc", "illegal character NUL"}, -} - -func TestScanErrors(t *testing.T) { - for _, e := range errors { - checkError(t, e.src, e.tok, e.pos, e.lit, e.err) - } -} - -// Verify that no comments show up as literal values when skipping comments. -func TestIssue10213(t *testing.T) { - const src = ` - var ( - A = 1 // foo - ) - - var ( - B = 2 - // foo - ) - - var C = 3 // foo - - var D = 4 - // foo - - func anycode() { - // foo - } - ` - var s Scanner - s.Init(fset.AddFile("", fset.Base(), len(src)), []byte(src), nil, 0) - for { - pos, tok, lit := s.Scan() - class := tokenclass(tok) - if lit != "" && class != keyword && class != literal && tok != token.SEMICOLON { - t.Errorf("%s: tok = %s, lit = %q", fset.Position(pos), tok, lit) - } - if tok <= token.EOF { - break - } - } -} - -func TestIssue28112(t *testing.T) { - const src = "... .. 0.. .." // make sure to have stand-alone ".." immediately before EOF to test EOF behavior - tokens := []token.Token{token.ELLIPSIS, token.PERIOD, token.PERIOD, token.FLOAT, token.PERIOD, token.PERIOD, token.PERIOD, token.EOF} - var s Scanner - s.Init(fset.AddFile("", fset.Base(), len(src)), []byte(src), nil, 0) - for _, want := range tokens { - pos, got, lit := s.Scan() - if got != want { - t.Errorf("%s: got %s, want %s", fset.Position(pos), got, want) - } - // literals expect to have a (non-empty) literal string and we don't care about other tokens for this test - if tokenclass(got) == literal && lit == "" { - t.Errorf("%s: for %s got empty literal string", fset.Position(pos), got) - } - } -} - -func BenchmarkScan(b *testing.B) { - b.StopTimer() - fset := token.NewFileSet() - file := fset.AddFile("", fset.Base(), len(source)) - var s Scanner - b.StartTimer() - for i := 0; i < b.N; i++ { - s.Init(file, source, nil, ScanComments) - for { - _, tok, _ := s.Scan() - if tok == token.EOF { - break - } - } - } -} - -func BenchmarkScanFiles(b *testing.B) { - // Scan a few arbitrary large files, and one small one, to provide some - // variety in benchmarks. - for _, p := range []string{ - "golang.org/x/website/internal/backport/go/types/expr.go", - "golang.org/x/website/internal/backport/go/parser/parser.go", - "net/http/server.go", - "golang.org/x/website/internal/backport/go/scanner/errors.go", - } { - b.Run(p, func(b *testing.B) { - b.StopTimer() - filename := filepath.Join("..", "..", filepath.FromSlash(p)) - src, err := os.ReadFile(filename) - if err != nil { - b.Fatal(err) - } - fset := token.NewFileSet() - file := fset.AddFile(filename, fset.Base(), len(src)) - b.SetBytes(int64(len(src))) - var s Scanner - b.StartTimer() - for i := 0; i < b.N; i++ { - s.Init(file, src, nil, ScanComments) - for { - _, tok, _ := s.Scan() - if tok == token.EOF { - break - } - } - } - }) - } -} - -func TestNumbers(t *testing.T) { - for _, test := range []struct { - tok token.Token - src, tokens, err string - }{ - // binaries - {token.INT, "0b0", "0b0", ""}, - {token.INT, "0b1010", "0b1010", ""}, - {token.INT, "0B1110", "0B1110", ""}, - - {token.INT, "0b", "0b", "binary literal has no digits"}, - {token.INT, "0b0190", "0b0190", "invalid digit '9' in binary literal"}, - {token.INT, "0b01a0", "0b01 a0", ""}, // only accept 0-9 - - {token.FLOAT, "0b.", "0b.", "invalid radix point in binary literal"}, - {token.FLOAT, "0b.1", "0b.1", "invalid radix point in binary literal"}, - {token.FLOAT, "0b1.0", "0b1.0", "invalid radix point in binary literal"}, - {token.FLOAT, "0b1e10", "0b1e10", "'e' exponent requires decimal mantissa"}, - {token.FLOAT, "0b1P-1", "0b1P-1", "'P' exponent requires hexadecimal mantissa"}, - - {token.IMAG, "0b10i", "0b10i", ""}, - {token.IMAG, "0b10.0i", "0b10.0i", "invalid radix point in binary literal"}, - - // octals - {token.INT, "0o0", "0o0", ""}, - {token.INT, "0o1234", "0o1234", ""}, - {token.INT, "0O1234", "0O1234", ""}, - - {token.INT, "0o", "0o", "octal literal has no digits"}, - {token.INT, "0o8123", "0o8123", "invalid digit '8' in octal literal"}, - {token.INT, "0o1293", "0o1293", "invalid digit '9' in octal literal"}, - {token.INT, "0o12a3", "0o12 a3", ""}, // only accept 0-9 - - {token.FLOAT, "0o.", "0o.", "invalid radix point in octal literal"}, - {token.FLOAT, "0o.2", "0o.2", "invalid radix point in octal literal"}, - {token.FLOAT, "0o1.2", "0o1.2", "invalid radix point in octal literal"}, - {token.FLOAT, "0o1E+2", "0o1E+2", "'E' exponent requires decimal mantissa"}, - {token.FLOAT, "0o1p10", "0o1p10", "'p' exponent requires hexadecimal mantissa"}, - - {token.IMAG, "0o10i", "0o10i", ""}, - {token.IMAG, "0o10e0i", "0o10e0i", "'e' exponent requires decimal mantissa"}, - - // 0-octals - {token.INT, "0", "0", ""}, - {token.INT, "0123", "0123", ""}, - - {token.INT, "08123", "08123", "invalid digit '8' in octal literal"}, - {token.INT, "01293", "01293", "invalid digit '9' in octal literal"}, - {token.INT, "0F.", "0 F .", ""}, // only accept 0-9 - {token.INT, "0123F.", "0123 F .", ""}, - {token.INT, "0123456x", "0123456 x", ""}, - - // decimals - {token.INT, "1", "1", ""}, - {token.INT, "1234", "1234", ""}, - - {token.INT, "1f", "1 f", ""}, // only accept 0-9 - - {token.IMAG, "0i", "0i", ""}, - {token.IMAG, "0678i", "0678i", ""}, - - // decimal floats - {token.FLOAT, "0.", "0.", ""}, - {token.FLOAT, "123.", "123.", ""}, - {token.FLOAT, "0123.", "0123.", ""}, - - {token.FLOAT, ".0", ".0", ""}, - {token.FLOAT, ".123", ".123", ""}, - {token.FLOAT, ".0123", ".0123", ""}, - - {token.FLOAT, "0.0", "0.0", ""}, - {token.FLOAT, "123.123", "123.123", ""}, - {token.FLOAT, "0123.0123", "0123.0123", ""}, - - {token.FLOAT, "0e0", "0e0", ""}, - {token.FLOAT, "123e+0", "123e+0", ""}, - {token.FLOAT, "0123E-1", "0123E-1", ""}, - - {token.FLOAT, "0.e+1", "0.e+1", ""}, - {token.FLOAT, "123.E-10", "123.E-10", ""}, - {token.FLOAT, "0123.e123", "0123.e123", ""}, - - {token.FLOAT, ".0e-1", ".0e-1", ""}, - {token.FLOAT, ".123E+10", ".123E+10", ""}, - {token.FLOAT, ".0123E123", ".0123E123", ""}, - - {token.FLOAT, "0.0e1", "0.0e1", ""}, - {token.FLOAT, "123.123E-10", "123.123E-10", ""}, - {token.FLOAT, "0123.0123e+456", "0123.0123e+456", ""}, - - {token.FLOAT, "0e", "0e", "exponent has no digits"}, - {token.FLOAT, "0E+", "0E+", "exponent has no digits"}, - {token.FLOAT, "1e+f", "1e+ f", "exponent has no digits"}, - {token.FLOAT, "0p0", "0p0", "'p' exponent requires hexadecimal mantissa"}, - {token.FLOAT, "1.0P-1", "1.0P-1", "'P' exponent requires hexadecimal mantissa"}, - - {token.IMAG, "0.i", "0.i", ""}, - {token.IMAG, ".123i", ".123i", ""}, - {token.IMAG, "123.123i", "123.123i", ""}, - {token.IMAG, "123e+0i", "123e+0i", ""}, - {token.IMAG, "123.E-10i", "123.E-10i", ""}, - {token.IMAG, ".123E+10i", ".123E+10i", ""}, - - // hexadecimals - {token.INT, "0x0", "0x0", ""}, - {token.INT, "0x1234", "0x1234", ""}, - {token.INT, "0xcafef00d", "0xcafef00d", ""}, - {token.INT, "0XCAFEF00D", "0XCAFEF00D", ""}, - - {token.INT, "0x", "0x", "hexadecimal literal has no digits"}, - {token.INT, "0x1g", "0x1 g", ""}, - - {token.IMAG, "0xf00i", "0xf00i", ""}, - - // hexadecimal floats - {token.FLOAT, "0x0p0", "0x0p0", ""}, - {token.FLOAT, "0x12efp-123", "0x12efp-123", ""}, - {token.FLOAT, "0xABCD.p+0", "0xABCD.p+0", ""}, - {token.FLOAT, "0x.0189P-0", "0x.0189P-0", ""}, - {token.FLOAT, "0x1.ffffp+1023", "0x1.ffffp+1023", ""}, - - {token.FLOAT, "0x.", "0x.", "hexadecimal literal has no digits"}, - {token.FLOAT, "0x0.", "0x0.", "hexadecimal mantissa requires a 'p' exponent"}, - {token.FLOAT, "0x.0", "0x.0", "hexadecimal mantissa requires a 'p' exponent"}, - {token.FLOAT, "0x1.1", "0x1.1", "hexadecimal mantissa requires a 'p' exponent"}, - {token.FLOAT, "0x1.1e0", "0x1.1e0", "hexadecimal mantissa requires a 'p' exponent"}, - {token.FLOAT, "0x1.2gp1a", "0x1.2 gp1a", "hexadecimal mantissa requires a 'p' exponent"}, - {token.FLOAT, "0x0p", "0x0p", "exponent has no digits"}, - {token.FLOAT, "0xeP-", "0xeP-", "exponent has no digits"}, - {token.FLOAT, "0x1234PAB", "0x1234P AB", "exponent has no digits"}, - {token.FLOAT, "0x1.2p1a", "0x1.2p1 a", ""}, - - {token.IMAG, "0xf00.bap+12i", "0xf00.bap+12i", ""}, - - // separators - {token.INT, "0b_1000_0001", "0b_1000_0001", ""}, - {token.INT, "0o_600", "0o_600", ""}, - {token.INT, "0_466", "0_466", ""}, - {token.INT, "1_000", "1_000", ""}, - {token.FLOAT, "1_000.000_1", "1_000.000_1", ""}, - {token.IMAG, "10e+1_2_3i", "10e+1_2_3i", ""}, - {token.INT, "0x_f00d", "0x_f00d", ""}, - {token.FLOAT, "0x_f00d.0p1_2", "0x_f00d.0p1_2", ""}, - - {token.INT, "0b__1000", "0b__1000", "'_' must separate successive digits"}, - {token.INT, "0o60___0", "0o60___0", "'_' must separate successive digits"}, - {token.INT, "0466_", "0466_", "'_' must separate successive digits"}, - {token.FLOAT, "1_.", "1_.", "'_' must separate successive digits"}, - {token.FLOAT, "0._1", "0._1", "'_' must separate successive digits"}, - {token.FLOAT, "2.7_e0", "2.7_e0", "'_' must separate successive digits"}, - {token.IMAG, "10e+12_i", "10e+12_i", "'_' must separate successive digits"}, - {token.INT, "0x___0", "0x___0", "'_' must separate successive digits"}, - {token.FLOAT, "0x1.0_p0", "0x1.0_p0", "'_' must separate successive digits"}, - } { - var s Scanner - var err string - s.Init(fset.AddFile("", fset.Base(), len(test.src)), []byte(test.src), func(_ token.Position, msg string) { - if err == "" { - err = msg - } - }, 0) - for i, want := range strings.Split(test.tokens, " ") { - err = "" - _, tok, lit := s.Scan() - - // compute lit where for tokens where lit is not defined - switch tok { - case token.PERIOD: - lit = "." - case token.ADD: - lit = "+" - case token.SUB: - lit = "-" - } - - if i == 0 { - if tok != test.tok { - t.Errorf("%q: got token %s; want %s", test.src, tok, test.tok) - } - if err != test.err { - t.Errorf("%q: got error %q; want %q", test.src, err, test.err) - } - } - - if lit != want { - t.Errorf("%q: got literal %q (%s); want %s", test.src, lit, tok, want) - } - } - - // make sure we read all - _, tok, _ := s.Scan() - if tok == token.SEMICOLON { - _, tok, _ = s.Scan() - } - if tok != token.EOF { - t.Errorf("%q: got %s; want EOF", test.src, tok) - } - } -} diff --git a/internal/backport/go/token/example_test.go b/internal/backport/go/token/example_test.go deleted file mode 100644 index e41a958c68..0000000000 --- a/internal/backport/go/token/example_test.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2018 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 token_test - -import ( - "fmt" - "golang.org/x/website/internal/backport/go/ast" - "golang.org/x/website/internal/backport/go/parser" - "golang.org/x/website/internal/backport/go/token" -) - -func Example_retrievePositionInfo() { - fset := token.NewFileSet() - - const src = `package main - -import "fmt" - -import "go/token" - -//line :1:5 -type p = token.Pos - -const bad = token.NoPos - -//line fake.go:42:11 -func ok(pos p) bool { - return pos != bad -} - -/*line :7:9*/func main() { - fmt.Println(ok(bad) == bad.IsValid()) -} -` - - f, err := parser.ParseFile(fset, "main.go", src, 0) - if err != nil { - fmt.Println(err) - return - } - - // Print the location and kind of each declaration in f. - for _, decl := range f.Decls { - // Get the filename, line, and column back via the file set. - // We get both the relative and absolute position. - // The relative position is relative to the last line directive. - // The absolute position is the exact position in the source. - pos := decl.Pos() - relPosition := fset.Position(pos) - absPosition := fset.PositionFor(pos, false) - - // Either a FuncDecl or GenDecl, since we exit on error. - kind := "func" - if gen, ok := decl.(*ast.GenDecl); ok { - kind = gen.Tok.String() - } - - // If the relative and absolute positions differ, show both. - fmtPosition := relPosition.String() - if relPosition != absPosition { - fmtPosition += "[" + absPosition.String() + "]" - } - - fmt.Printf("%s: %s\n", fmtPosition, kind) - } - - //Output: - // - // main.go:3:1: import - // main.go:5:1: import - // main.go:1:5[main.go:8:1]: type - // main.go:3:1[main.go:10:1]: const - // fake.go:42:11[main.go:13:1]: func - // fake.go:7:9[main.go:17:14]: func -} diff --git a/internal/backport/go/token/position.go b/internal/backport/go/token/position.go deleted file mode 100644 index 00f24535bf..0000000000 --- a/internal/backport/go/token/position.go +++ /dev/null @@ -1,529 +0,0 @@ -// Copyright 2010 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 token - -import ( - "fmt" - "sort" - "sync" -) - -// ----------------------------------------------------------------------------- -// Positions - -// Position describes an arbitrary source position -// including the file, line, and column location. -// A Position is valid if the line number is > 0. -type Position struct { - Filename string // filename, if any - Offset int // offset, starting at 0 - Line int // line number, starting at 1 - Column int // column number, starting at 1 (byte count) -} - -// IsValid reports whether the position is valid. -func (pos *Position) IsValid() bool { return pos.Line > 0 } - -// String returns a string in one of several forms: -// -// file:line:column valid position with file name -// file:line valid position with file name but no column (column == 0) -// line:column valid position without file name -// line valid position without file name and no column (column == 0) -// file invalid position with file name -// - invalid position without file name -func (pos Position) String() string { - s := pos.Filename - if pos.IsValid() { - if s != "" { - s += ":" - } - s += fmt.Sprintf("%d", pos.Line) - if pos.Column != 0 { - s += fmt.Sprintf(":%d", pos.Column) - } - } - if s == "" { - s = "-" - } - return s -} - -// Pos is a compact encoding of a source position within a file set. -// It can be converted into a Position for a more convenient, but much -// larger, representation. -// -// The Pos value for a given file is a number in the range [base, base+size], -// where base and size are specified when a file is added to the file set. -// The difference between a Pos value and the corresponding file base -// corresponds to the byte offset of that position (represented by the Pos value) -// from the beginning of the file. Thus, the file base offset is the Pos value -// representing the first byte in the file. -// -// To create the Pos value for a specific source offset (measured in bytes), -// first add the respective file to the current file set using FileSet.AddFile -// and then call File.Pos(offset) for that file. Given a Pos value p -// for a specific file set fset, the corresponding Position value is -// obtained by calling fset.Position(p). -// -// Pos values can be compared directly with the usual comparison operators: -// If two Pos values p and q are in the same file, comparing p and q is -// equivalent to comparing the respective source file offsets. If p and q -// are in different files, p < q is true if the file implied by p was added -// to the respective file set before the file implied by q. -type Pos int - -// The zero value for Pos is NoPos; there is no file and line information -// associated with it, and NoPos.IsValid() is false. NoPos is always -// smaller than any other Pos value. The corresponding Position value -// for NoPos is the zero value for Position. -const NoPos Pos = 0 - -// IsValid reports whether the position is valid. -func (p Pos) IsValid() bool { - return p != NoPos -} - -// ----------------------------------------------------------------------------- -// File - -// A File is a handle for a file belonging to a FileSet. -// A File has a name, size, and line offset table. -type File struct { - set *FileSet - name string // file name as provided to AddFile - base int // Pos value range for this file is [base...base+size] - size int // file size as provided to AddFile - - // lines and infos are protected by mutex - mutex sync.Mutex - lines []int // lines contains the offset of the first character for each line (the first entry is always 0) - infos []lineInfo -} - -// Name returns the file name of file f as registered with AddFile. -func (f *File) Name() string { - return f.name -} - -// Base returns the base offset of file f as registered with AddFile. -func (f *File) Base() int { - return f.base -} - -// Size returns the size of file f as registered with AddFile. -func (f *File) Size() int { - return f.size -} - -// LineCount returns the number of lines in file f. -func (f *File) LineCount() int { - f.mutex.Lock() - n := len(f.lines) - f.mutex.Unlock() - return n -} - -// AddLine adds the line offset for a new line. -// The line offset must be larger than the offset for the previous line -// and smaller than the file size; otherwise the line offset is ignored. -func (f *File) AddLine(offset int) { - f.mutex.Lock() - if i := len(f.lines); (i == 0 || f.lines[i-1] < offset) && offset < f.size { - f.lines = append(f.lines, offset) - } - f.mutex.Unlock() -} - -// MergeLine merges a line with the following line. It is akin to replacing -// the newline character at the end of the line with a space (to not change the -// remaining offsets). To obtain the line number, consult e.g. Position.Line. -// MergeLine will panic if given an invalid line number. -func (f *File) MergeLine(line int) { - if line < 1 { - panic(fmt.Sprintf("invalid line number %d (should be >= 1)", line)) - } - f.mutex.Lock() - defer f.mutex.Unlock() - if line >= len(f.lines) { - panic(fmt.Sprintf("invalid line number %d (should be < %d)", line, len(f.lines))) - } - // To merge the line numbered with the line numbered , - // we need to remove the entry in lines corresponding to the line - // numbered . The entry in lines corresponding to the line - // numbered is located at index , since indices in lines - // are 0-based and line numbers are 1-based. - copy(f.lines[line:], f.lines[line+1:]) - f.lines = f.lines[:len(f.lines)-1] -} - -// SetLines sets the line offsets for a file and reports whether it succeeded. -// The line offsets are the offsets of the first character of each line; -// for instance for the content "ab\nc\n" the line offsets are {0, 3}. -// An empty file has an empty line offset table. -// Each line offset must be larger than the offset for the previous line -// and smaller than the file size; otherwise SetLines fails and returns -// false. -// Callers must not mutate the provided slice after SetLines returns. -func (f *File) SetLines(lines []int) bool { - // verify validity of lines table - size := f.size - for i, offset := range lines { - if i > 0 && offset <= lines[i-1] || size <= offset { - return false - } - } - - // set lines table - f.mutex.Lock() - f.lines = lines - f.mutex.Unlock() - return true -} - -// SetLinesForContent sets the line offsets for the given file content. -// It ignores position-altering //line comments. -func (f *File) SetLinesForContent(content []byte) { - var lines []int - line := 0 - for offset, b := range content { - if line >= 0 { - lines = append(lines, line) - } - line = -1 - if b == '\n' { - line = offset + 1 - } - } - - // set lines table - f.mutex.Lock() - f.lines = lines - f.mutex.Unlock() -} - -// LineStart returns the Pos value of the start of the specified line. -// It ignores any alternative positions set using AddLineColumnInfo. -// LineStart panics if the 1-based line number is invalid. -func (f *File) LineStart(line int) Pos { - if line < 1 { - panic(fmt.Sprintf("invalid line number %d (should be >= 1)", line)) - } - f.mutex.Lock() - defer f.mutex.Unlock() - if line > len(f.lines) { - panic(fmt.Sprintf("invalid line number %d (should be < %d)", line, len(f.lines))) - } - return Pos(f.base + f.lines[line-1]) -} - -// A lineInfo object describes alternative file, line, and column -// number information (such as provided via a //line directive) -// for a given file offset. -type lineInfo struct { - // fields are exported to make them accessible to gob - Offset int - Filename string - Line, Column int -} - -// AddLineInfo is like AddLineColumnInfo with a column = 1 argument. -// It is here for backward-compatibility for code prior to Go 1.11. -func (f *File) AddLineInfo(offset int, filename string, line int) { - f.AddLineColumnInfo(offset, filename, line, 1) -} - -// AddLineColumnInfo adds alternative file, line, and column number -// information for a given file offset. The offset must be larger -// than the offset for the previously added alternative line info -// and smaller than the file size; otherwise the information is -// ignored. -// -// AddLineColumnInfo is typically used to register alternative position -// information for line directives such as //line filename:line:column. -func (f *File) AddLineColumnInfo(offset int, filename string, line, column int) { - f.mutex.Lock() - if i := len(f.infos); i == 0 || f.infos[i-1].Offset < offset && offset < f.size { - f.infos = append(f.infos, lineInfo{offset, filename, line, column}) - } - f.mutex.Unlock() -} - -// Pos returns the Pos value for the given file offset; -// the offset must be <= f.Size(). -// f.Pos(f.Offset(p)) == p. -func (f *File) Pos(offset int) Pos { - if offset > f.size { - panic(fmt.Sprintf("invalid file offset %d (should be <= %d)", offset, f.size)) - } - return Pos(f.base + offset) -} - -// Offset returns the offset for the given file position p; -// p must be a valid Pos value in that file. -// f.Offset(f.Pos(offset)) == offset. -func (f *File) Offset(p Pos) int { - if int(p) < f.base || int(p) > f.base+f.size { - panic(fmt.Sprintf("invalid Pos value %d (should be in [%d, %d])", p, f.base, f.base+f.size)) - } - return int(p) - f.base -} - -// Line returns the line number for the given file position p; -// p must be a Pos value in that file or NoPos. -func (f *File) Line(p Pos) int { - return f.Position(p).Line -} - -func searchLineInfos(a []lineInfo, x int) int { - return sort.Search(len(a), func(i int) bool { return a[i].Offset > x }) - 1 -} - -// unpack returns the filename and line and column number for a file offset. -// If adjusted is set, unpack will return the filename and line information -// possibly adjusted by //line comments; otherwise those comments are ignored. -func (f *File) unpack(offset int, adjusted bool) (filename string, line, column int) { - f.mutex.Lock() - defer f.mutex.Unlock() - filename = f.name - if i := searchInts(f.lines, offset); i >= 0 { - line, column = i+1, offset-f.lines[i]+1 - } - if adjusted && len(f.infos) > 0 { - // few files have extra line infos - if i := searchLineInfos(f.infos, offset); i >= 0 { - alt := &f.infos[i] - filename = alt.Filename - if i := searchInts(f.lines, alt.Offset); i >= 0 { - // i+1 is the line at which the alternative position was recorded - d := line - (i + 1) // line distance from alternative position base - line = alt.Line + d - if alt.Column == 0 { - // alternative column is unknown => relative column is unknown - // (the current specification for line directives requires - // this to apply until the next PosBase/line directive, - // not just until the new newline) - column = 0 - } else if d == 0 { - // the alternative position base is on the current line - // => column is relative to alternative column - column = alt.Column + (offset - alt.Offset) - } - } - } - } - return -} - -func (f *File) position(p Pos, adjusted bool) (pos Position) { - offset := int(p) - f.base - pos.Offset = offset - pos.Filename, pos.Line, pos.Column = f.unpack(offset, adjusted) - return -} - -// PositionFor returns the Position value for the given file position p. -// If adjusted is set, the position may be adjusted by position-altering -// //line comments; otherwise those comments are ignored. -// p must be a Pos value in f or NoPos. -func (f *File) PositionFor(p Pos, adjusted bool) (pos Position) { - if p != NoPos { - if int(p) < f.base || int(p) > f.base+f.size { - panic(fmt.Sprintf("invalid Pos value %d (should be in [%d, %d])", p, f.base, f.base+f.size)) - } - pos = f.position(p, adjusted) - } - return -} - -// Position returns the Position value for the given file position p. -// Calling f.Position(p) is equivalent to calling f.PositionFor(p, true). -func (f *File) Position(p Pos) (pos Position) { - return f.PositionFor(p, true) -} - -// ----------------------------------------------------------------------------- -// FileSet - -// A FileSet represents a set of source files. -// Methods of file sets are synchronized; multiple goroutines -// may invoke them concurrently. -// -// The byte offsets for each file in a file set are mapped into -// distinct (integer) intervals, one interval [base, base+size] -// per file. Base represents the first byte in the file, and size -// is the corresponding file size. A Pos value is a value in such -// an interval. By determining the interval a Pos value belongs -// to, the file, its file base, and thus the byte offset (position) -// the Pos value is representing can be computed. -// -// When adding a new file, a file base must be provided. That can -// be any integer value that is past the end of any interval of any -// file already in the file set. For convenience, FileSet.Base provides -// such a value, which is simply the end of the Pos interval of the most -// recently added file, plus one. Unless there is a need to extend an -// interval later, using the FileSet.Base should be used as argument -// for FileSet.AddFile. -type FileSet struct { - mutex sync.RWMutex // protects the file set - base int // base offset for the next file - files []*File // list of files in the order added to the set - last *File // cache of last file looked up -} - -// NewFileSet creates a new file set. -func NewFileSet() *FileSet { - return &FileSet{ - base: 1, // 0 == NoPos - } -} - -// Base returns the minimum base offset that must be provided to -// AddFile when adding the next file. -func (s *FileSet) Base() int { - s.mutex.RLock() - b := s.base - s.mutex.RUnlock() - return b - -} - -// AddFile adds a new file with a given filename, base offset, and file size -// to the file set s and returns the file. Multiple files may have the same -// name. The base offset must not be smaller than the FileSet's Base(), and -// size must not be negative. As a special case, if a negative base is provided, -// the current value of the FileSet's Base() is used instead. -// -// Adding the file will set the file set's Base() value to base + size + 1 -// as the minimum base value for the next file. The following relationship -// exists between a Pos value p for a given file offset offs: -// -// int(p) = base + offs -// -// with offs in the range [0, size] and thus p in the range [base, base+size]. -// For convenience, File.Pos may be used to create file-specific position -// values from a file offset. -func (s *FileSet) AddFile(filename string, base, size int) *File { - s.mutex.Lock() - defer s.mutex.Unlock() - if base < 0 { - base = s.base - } - if base < s.base { - panic(fmt.Sprintf("invalid base %d (should be >= %d)", base, s.base)) - } - if size < 0 { - panic(fmt.Sprintf("invalid size %d (should be >= 0)", size)) - } - // base >= s.base && size >= 0 - f := &File{set: s, name: filename, base: base, size: size, lines: []int{0}} - base += size + 1 // +1 because EOF also has a position - if base < 0 { - panic("token.Pos offset overflow (> 2G of source code in file set)") - } - // add the file to the file set - s.base = base - s.files = append(s.files, f) - s.last = f - return f -} - -// Iterate calls f for the files in the file set in the order they were added -// until f returns false. -func (s *FileSet) Iterate(f func(*File) bool) { - for i := 0; ; i++ { - var file *File - s.mutex.RLock() - if i < len(s.files) { - file = s.files[i] - } - s.mutex.RUnlock() - if file == nil || !f(file) { - break - } - } -} - -func searchFiles(a []*File, x int) int { - return sort.Search(len(a), func(i int) bool { return a[i].base > x }) - 1 -} - -func (s *FileSet) file(p Pos) *File { - s.mutex.RLock() - // common case: p is in last file - if f := s.last; f != nil && f.base <= int(p) && int(p) <= f.base+f.size { - s.mutex.RUnlock() - return f - } - // p is not in last file - search all files - if i := searchFiles(s.files, int(p)); i >= 0 { - f := s.files[i] - // f.base <= int(p) by definition of searchFiles - if int(p) <= f.base+f.size { - s.mutex.RUnlock() - s.mutex.Lock() - s.last = f // race is ok - s.last is only a cache - s.mutex.Unlock() - return f - } - } - s.mutex.RUnlock() - return nil -} - -// File returns the file that contains the position p. -// If no such file is found (for instance for p == NoPos), -// the result is nil. -func (s *FileSet) File(p Pos) (f *File) { - if p != NoPos { - f = s.file(p) - } - return -} - -// PositionFor converts a Pos p in the fileset into a Position value. -// If adjusted is set, the position may be adjusted by position-altering -// //line comments; otherwise those comments are ignored. -// p must be a Pos value in s or NoPos. -func (s *FileSet) PositionFor(p Pos, adjusted bool) (pos Position) { - if p != NoPos { - if f := s.file(p); f != nil { - return f.position(p, adjusted) - } - } - return -} - -// Position converts a Pos p in the fileset into a Position value. -// Calling s.Position(p) is equivalent to calling s.PositionFor(p, true). -func (s *FileSet) Position(p Pos) (pos Position) { - return s.PositionFor(p, true) -} - -// ----------------------------------------------------------------------------- -// Helper functions - -func searchInts(a []int, x int) int { - // This function body is a manually inlined version of: - // - // return sort.Search(len(a), func(i int) bool { return a[i] > x }) - 1 - // - // With better compiler optimizations, this may not be needed in the - // future, but at the moment this change improves the go/printer - // benchmark performance by ~30%. This has a direct impact on the - // speed of gofmt and thus seems worthwhile (2011-04-29). - // TODO(gri): Remove this when compilers have caught up. - i, j := 0, len(a) - for i < j { - h := int(uint(i+j) >> 1) // avoid overflow when computing h - // i ≤ h < j - if a[h] <= x { - i = h + 1 - } else { - j = h - } - } - return i - 1 -} diff --git a/internal/backport/go/token/position_bench_test.go b/internal/backport/go/token/position_bench_test.go deleted file mode 100644 index 41be7285b7..0000000000 --- a/internal/backport/go/token/position_bench_test.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2021 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 token - -import ( - "testing" -) - -func BenchmarkSearchInts(b *testing.B) { - data := make([]int, 10000) - for i := 0; i < 10000; i++ { - data[i] = i - } - const x = 8 - if r := searchInts(data, x); r != x { - b.Errorf("got index = %d; want %d", r, x) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - searchInts(data, x) - } -} diff --git a/internal/backport/go/token/position_test.go b/internal/backport/go/token/position_test.go deleted file mode 100644 index 7d465dffa6..0000000000 --- a/internal/backport/go/token/position_test.go +++ /dev/null @@ -1,341 +0,0 @@ -// Copyright 2010 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 token - -import ( - "fmt" - "math/rand" - "sync" - "testing" -) - -func checkPos(t *testing.T, msg string, got, want Position) { - if got.Filename != want.Filename { - t.Errorf("%s: got filename = %q; want %q", msg, got.Filename, want.Filename) - } - if got.Offset != want.Offset { - t.Errorf("%s: got offset = %d; want %d", msg, got.Offset, want.Offset) - } - if got.Line != want.Line { - t.Errorf("%s: got line = %d; want %d", msg, got.Line, want.Line) - } - if got.Column != want.Column { - t.Errorf("%s: got column = %d; want %d", msg, got.Column, want.Column) - } -} - -func TestNoPos(t *testing.T) { - if NoPos.IsValid() { - t.Errorf("NoPos should not be valid") - } - var fset *FileSet - checkPos(t, "nil NoPos", fset.Position(NoPos), Position{}) - fset = NewFileSet() - checkPos(t, "fset NoPos", fset.Position(NoPos), Position{}) -} - -var tests = []struct { - filename string - source []byte // may be nil - size int - lines []int -}{ - {"a", []byte{}, 0, []int{}}, - {"b", []byte("01234"), 5, []int{0}}, - {"c", []byte("\n\n\n\n\n\n\n\n\n"), 9, []int{0, 1, 2, 3, 4, 5, 6, 7, 8}}, - {"d", nil, 100, []int{0, 5, 10, 20, 30, 70, 71, 72, 80, 85, 90, 99}}, - {"e", nil, 777, []int{0, 80, 100, 120, 130, 180, 267, 455, 500, 567, 620}}, - {"f", []byte("package p\n\nimport \"fmt\""), 23, []int{0, 10, 11}}, - {"g", []byte("package p\n\nimport \"fmt\"\n"), 24, []int{0, 10, 11}}, - {"h", []byte("package p\n\nimport \"fmt\"\n "), 25, []int{0, 10, 11, 24}}, -} - -func linecol(lines []int, offs int) (int, int) { - prevLineOffs := 0 - for line, lineOffs := range lines { - if offs < lineOffs { - return line, offs - prevLineOffs + 1 - } - prevLineOffs = lineOffs - } - return len(lines), offs - prevLineOffs + 1 -} - -func verifyPositions(t *testing.T, fset *FileSet, f *File, lines []int) { - for offs := 0; offs < f.Size(); offs++ { - p := f.Pos(offs) - offs2 := f.Offset(p) - if offs2 != offs { - t.Errorf("%s, Offset: got offset %d; want %d", f.Name(), offs2, offs) - } - line, col := linecol(lines, offs) - msg := fmt.Sprintf("%s (offs = %d, p = %d)", f.Name(), offs, p) - checkPos(t, msg, f.Position(f.Pos(offs)), Position{f.Name(), offs, line, col}) - checkPos(t, msg, fset.Position(p), Position{f.Name(), offs, line, col}) - } -} - -func makeTestSource(size int, lines []int) []byte { - src := make([]byte, size) - for _, offs := range lines { - if offs > 0 { - src[offs-1] = '\n' - } - } - return src -} - -func TestPositions(t *testing.T) { - const delta = 7 // a non-zero base offset increment - fset := NewFileSet() - for _, test := range tests { - // verify consistency of test case - if test.source != nil && len(test.source) != test.size { - t.Errorf("%s: inconsistent test case: got file size %d; want %d", test.filename, len(test.source), test.size) - } - - // add file and verify name and size - f := fset.AddFile(test.filename, fset.Base()+delta, test.size) - if f.Name() != test.filename { - t.Errorf("got filename %q; want %q", f.Name(), test.filename) - } - if f.Size() != test.size { - t.Errorf("%s: got file size %d; want %d", f.Name(), f.Size(), test.size) - } - if fset.File(f.Pos(0)) != f { - t.Errorf("%s: f.Pos(0) was not found in f", f.Name()) - } - - // add lines individually and verify all positions - for i, offset := range test.lines { - f.AddLine(offset) - if f.LineCount() != i+1 { - t.Errorf("%s, AddLine: got line count %d; want %d", f.Name(), f.LineCount(), i+1) - } - // adding the same offset again should be ignored - f.AddLine(offset) - if f.LineCount() != i+1 { - t.Errorf("%s, AddLine: got unchanged line count %d; want %d", f.Name(), f.LineCount(), i+1) - } - verifyPositions(t, fset, f, test.lines[0:i+1]) - } - - // add lines with SetLines and verify all positions - if ok := f.SetLines(test.lines); !ok { - t.Errorf("%s: SetLines failed", f.Name()) - } - if f.LineCount() != len(test.lines) { - t.Errorf("%s, SetLines: got line count %d; want %d", f.Name(), f.LineCount(), len(test.lines)) - } - verifyPositions(t, fset, f, test.lines) - - // add lines with SetLinesForContent and verify all positions - src := test.source - if src == nil { - // no test source available - create one from scratch - src = makeTestSource(test.size, test.lines) - } - f.SetLinesForContent(src) - if f.LineCount() != len(test.lines) { - t.Errorf("%s, SetLinesForContent: got line count %d; want %d", f.Name(), f.LineCount(), len(test.lines)) - } - verifyPositions(t, fset, f, test.lines) - } -} - -func TestLineInfo(t *testing.T) { - fset := NewFileSet() - f := fset.AddFile("foo", fset.Base(), 500) - lines := []int{0, 42, 77, 100, 210, 220, 277, 300, 333, 401} - // add lines individually and provide alternative line information - for _, offs := range lines { - f.AddLine(offs) - f.AddLineInfo(offs, "bar", 42) - } - // verify positions for all offsets - for offs := 0; offs <= f.Size(); offs++ { - p := f.Pos(offs) - _, col := linecol(lines, offs) - msg := fmt.Sprintf("%s (offs = %d, p = %d)", f.Name(), offs, p) - checkPos(t, msg, f.Position(f.Pos(offs)), Position{"bar", offs, 42, col}) - checkPos(t, msg, fset.Position(p), Position{"bar", offs, 42, col}) - } -} - -func TestFiles(t *testing.T) { - fset := NewFileSet() - for i, test := range tests { - base := fset.Base() - if i%2 == 1 { - // Setting a negative base is equivalent to - // fset.Base(), so test some of each. - base = -1 - } - fset.AddFile(test.filename, base, test.size) - j := 0 - fset.Iterate(func(f *File) bool { - if f.Name() != tests[j].filename { - t.Errorf("got filename = %s; want %s", f.Name(), tests[j].filename) - } - j++ - return true - }) - if j != i+1 { - t.Errorf("got %d files; want %d", j, i+1) - } - } -} - -// FileSet.File should return nil if Pos is past the end of the FileSet. -func TestFileSetPastEnd(t *testing.T) { - fset := NewFileSet() - for _, test := range tests { - fset.AddFile(test.filename, fset.Base(), test.size) - } - if f := fset.File(Pos(fset.Base())); f != nil { - t.Errorf("got %v, want nil", f) - } -} - -func TestFileSetCacheUnlikely(t *testing.T) { - fset := NewFileSet() - offsets := make(map[string]int) - for _, test := range tests { - offsets[test.filename] = fset.Base() - fset.AddFile(test.filename, fset.Base(), test.size) - } - for file, pos := range offsets { - f := fset.File(Pos(pos)) - if f.Name() != file { - t.Errorf("got %q at position %d, want %q", f.Name(), pos, file) - } - } -} - -// issue 4345. Test that concurrent use of FileSet.Pos does not trigger a -// race in the FileSet position cache. -func TestFileSetRace(t *testing.T) { - fset := NewFileSet() - for i := 0; i < 100; i++ { - fset.AddFile(fmt.Sprintf("file-%d", i), fset.Base(), 1031) - } - max := int32(fset.Base()) - var stop sync.WaitGroup - r := rand.New(rand.NewSource(7)) - for i := 0; i < 2; i++ { - r := rand.New(rand.NewSource(r.Int63())) - stop.Add(1) - go func() { - for i := 0; i < 1000; i++ { - fset.Position(Pos(r.Int31n(max))) - } - stop.Done() - }() - } - stop.Wait() -} - -// issue 16548. Test that concurrent use of File.AddLine and FileSet.PositionFor -// does not trigger a race in the FileSet position cache. -func TestFileSetRace2(t *testing.T) { - const N = 1e3 - var ( - fset = NewFileSet() - file = fset.AddFile("", -1, N) - ch = make(chan int, 2) - ) - - go func() { - for i := 0; i < N; i++ { - file.AddLine(i) - } - ch <- 1 - }() - - go func() { - pos := file.Pos(0) - for i := 0; i < N; i++ { - fset.PositionFor(pos, false) - } - ch <- 1 - }() - - <-ch - <-ch -} - -func TestPositionFor(t *testing.T) { - src := []byte(` -foo -b -ar -//line :100 -foobar -//line bar:3 -done -`) - - const filename = "foo" - fset := NewFileSet() - f := fset.AddFile(filename, fset.Base(), len(src)) - f.SetLinesForContent(src) - - // verify position info - for i, offs := range f.lines { - got1 := f.PositionFor(f.Pos(offs), false) - got2 := f.PositionFor(f.Pos(offs), true) - got3 := f.Position(f.Pos(offs)) - want := Position{filename, offs, i + 1, 1} - checkPos(t, "1. PositionFor unadjusted", got1, want) - checkPos(t, "1. PositionFor adjusted", got2, want) - checkPos(t, "1. Position", got3, want) - } - - // manually add //line info on lines l1, l2 - const l1, l2 = 5, 7 - f.AddLineInfo(f.lines[l1-1], "", 100) - f.AddLineInfo(f.lines[l2-1], "bar", 3) - - // unadjusted position info must remain unchanged - for i, offs := range f.lines { - got1 := f.PositionFor(f.Pos(offs), false) - want := Position{filename, offs, i + 1, 1} - checkPos(t, "2. PositionFor unadjusted", got1, want) - } - - // adjusted position info should have changed - for i, offs := range f.lines { - got2 := f.PositionFor(f.Pos(offs), true) - got3 := f.Position(f.Pos(offs)) - want := Position{filename, offs, i + 1, 1} - // manually compute wanted filename and line - line := want.Line - if i+1 >= l1 { - want.Filename = "" - want.Line = line - l1 + 100 - } - if i+1 >= l2 { - want.Filename = "bar" - want.Line = line - l2 + 3 - } - checkPos(t, "3. PositionFor adjusted", got2, want) - checkPos(t, "3. Position", got3, want) - } -} - -func TestLineStart(t *testing.T) { - const src = "one\ntwo\nthree\n" - fset := NewFileSet() - f := fset.AddFile("input", -1, len(src)) - f.SetLinesForContent([]byte(src)) - - for line := 1; line <= 3; line++ { - pos := f.LineStart(line) - position := fset.Position(pos) - if position.Line != line || position.Column != 1 { - t.Errorf("LineStart(%d) returned wrong pos %d: %s", line, pos, position) - } - } -} diff --git a/internal/backport/go/token/serialize.go b/internal/backport/go/token/serialize.go deleted file mode 100644 index d0ea34517a..0000000000 --- a/internal/backport/go/token/serialize.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2011 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 token - -type serializedFile struct { - // fields correspond 1:1 to fields with same (lower-case) name in File - Name string - Base int - Size int - Lines []int - Infos []lineInfo -} - -type serializedFileSet struct { - Base int - Files []serializedFile -} - -// Read calls decode to deserialize a file set into s; s must not be nil. -func (s *FileSet) Read(decode func(interface{}) error) error { - var ss serializedFileSet - if err := decode(&ss); err != nil { - return err - } - - s.mutex.Lock() - s.base = ss.Base - files := make([]*File, len(ss.Files)) - for i := 0; i < len(ss.Files); i++ { - f := &ss.Files[i] - files[i] = &File{ - set: s, - name: f.Name, - base: f.Base, - size: f.Size, - lines: f.Lines, - infos: f.Infos, - } - } - s.files = files - s.last = nil - s.mutex.Unlock() - - return nil -} - -// Write calls encode to serialize the file set s. -func (s *FileSet) Write(encode func(interface{}) error) error { - var ss serializedFileSet - - s.mutex.Lock() - ss.Base = s.base - files := make([]serializedFile, len(s.files)) - for i, f := range s.files { - f.mutex.Lock() - files[i] = serializedFile{ - Name: f.name, - Base: f.base, - Size: f.size, - Lines: append([]int(nil), f.lines...), - Infos: append([]lineInfo(nil), f.infos...), - } - f.mutex.Unlock() - } - ss.Files = files - s.mutex.Unlock() - - return encode(ss) -} diff --git a/internal/backport/go/token/serialize_test.go b/internal/backport/go/token/serialize_test.go deleted file mode 100644 index 4e925adb6f..0000000000 --- a/internal/backport/go/token/serialize_test.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2011 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 token - -import ( - "bytes" - "encoding/gob" - "fmt" - "testing" -) - -// equal returns nil if p and q describe the same file set; -// otherwise it returns an error describing the discrepancy. -func equal(p, q *FileSet) error { - if p == q { - // avoid deadlock if p == q - return nil - } - - // not strictly needed for the test - p.mutex.Lock() - q.mutex.Lock() - defer q.mutex.Unlock() - defer p.mutex.Unlock() - - if p.base != q.base { - return fmt.Errorf("different bases: %d != %d", p.base, q.base) - } - - if len(p.files) != len(q.files) { - return fmt.Errorf("different number of files: %d != %d", len(p.files), len(q.files)) - } - - for i, f := range p.files { - g := q.files[i] - if f.set != p { - return fmt.Errorf("wrong fileset for %q", f.name) - } - if g.set != q { - return fmt.Errorf("wrong fileset for %q", g.name) - } - if f.name != g.name { - return fmt.Errorf("different filenames: %q != %q", f.name, g.name) - } - if f.base != g.base { - return fmt.Errorf("different base for %q: %d != %d", f.name, f.base, g.base) - } - if f.size != g.size { - return fmt.Errorf("different size for %q: %d != %d", f.name, f.size, g.size) - } - for j, l := range f.lines { - m := g.lines[j] - if l != m { - return fmt.Errorf("different offsets for %q", f.name) - } - } - for j, l := range f.infos { - m := g.infos[j] - if l.Offset != m.Offset || l.Filename != m.Filename || l.Line != m.Line { - return fmt.Errorf("different infos for %q", f.name) - } - } - } - - // we don't care about .last - it's just a cache - return nil -} - -func checkSerialize(t *testing.T, p *FileSet) { - var buf bytes.Buffer - encode := func(x interface{}) error { - return gob.NewEncoder(&buf).Encode(x) - } - if err := p.Write(encode); err != nil { - t.Errorf("writing fileset failed: %s", err) - return - } - q := NewFileSet() - decode := func(x interface{}) error { - return gob.NewDecoder(&buf).Decode(x) - } - if err := q.Read(decode); err != nil { - t.Errorf("reading fileset failed: %s", err) - return - } - if err := equal(p, q); err != nil { - t.Errorf("filesets not identical: %s", err) - } -} - -func TestSerialization(t *testing.T) { - p := NewFileSet() - checkSerialize(t, p) - // add some files - for i := 0; i < 10; i++ { - f := p.AddFile(fmt.Sprintf("file%d", i), p.Base()+i, i*100) - checkSerialize(t, p) - // add some lines and alternative file infos - line := 1000 - for offs := 0; offs < f.Size(); offs += 40 + i { - f.AddLine(offs) - if offs%7 == 0 { - f.AddLineInfo(offs, fmt.Sprintf("file%d", offs), line) - line += 33 - } - } - checkSerialize(t, p) - } -} diff --git a/internal/backport/go/token/token.go b/internal/backport/go/token/token.go deleted file mode 100644 index db4e95577c..0000000000 --- a/internal/backport/go/token/token.go +++ /dev/null @@ -1,338 +0,0 @@ -// Copyright 2009 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 token defines constants representing the lexical tokens of the Go -// programming language and basic operations on tokens (printing, predicates). -package token - -import ( - "strconv" - "unicode" - "unicode/utf8" -) - -// Token is the set of lexical tokens of the Go programming language. -type Token int - -// The list of tokens. -const ( - // Special tokens - ILLEGAL Token = iota - EOF - COMMENT - - literal_beg - // Identifiers and basic type literals - // (these tokens stand for classes of literals) - IDENT // main - INT // 12345 - FLOAT // 123.45 - IMAG // 123.45i - CHAR // 'a' - STRING // "abc" - literal_end - - operator_beg - // Operators and delimiters - ADD // + - SUB // - - MUL // * - QUO // / - REM // % - - AND // & - OR // | - XOR // ^ - SHL // << - SHR // >> - AND_NOT // &^ - - ADD_ASSIGN // += - SUB_ASSIGN // -= - MUL_ASSIGN // *= - QUO_ASSIGN // /= - REM_ASSIGN // %= - - AND_ASSIGN // &= - OR_ASSIGN // |= - XOR_ASSIGN // ^= - SHL_ASSIGN // <<= - SHR_ASSIGN // >>= - AND_NOT_ASSIGN // &^= - - LAND // && - LOR // || - ARROW // <- - INC // ++ - DEC // -- - - EQL // == - LSS // < - GTR // > - ASSIGN // = - NOT // ! - - NEQ // != - LEQ // <= - GEQ // >= - DEFINE // := - ELLIPSIS // ... - - LPAREN // ( - LBRACK // [ - LBRACE // { - COMMA // , - PERIOD // . - - RPAREN // ) - RBRACK // ] - RBRACE // } - SEMICOLON // ; - COLON // : - operator_end - - keyword_beg - // Keywords - BREAK - CASE - CHAN - CONST - CONTINUE - - DEFAULT - DEFER - ELSE - FALLTHROUGH - FOR - - FUNC - GO - GOTO - IF - IMPORT - - INTERFACE - MAP - PACKAGE - RANGE - RETURN - - SELECT - STRUCT - SWITCH - TYPE - VAR - keyword_end - - additional_beg - // additional tokens, handled in an ad-hoc manner - TILDE - additional_end -) - -var tokens = [...]string{ - ILLEGAL: "ILLEGAL", - - EOF: "EOF", - COMMENT: "COMMENT", - - IDENT: "IDENT", - INT: "INT", - FLOAT: "FLOAT", - IMAG: "IMAG", - CHAR: "CHAR", - STRING: "STRING", - - ADD: "+", - SUB: "-", - MUL: "*", - QUO: "/", - REM: "%", - - AND: "&", - OR: "|", - XOR: "^", - SHL: "<<", - SHR: ">>", - AND_NOT: "&^", - - ADD_ASSIGN: "+=", - SUB_ASSIGN: "-=", - MUL_ASSIGN: "*=", - QUO_ASSIGN: "/=", - REM_ASSIGN: "%=", - - AND_ASSIGN: "&=", - OR_ASSIGN: "|=", - XOR_ASSIGN: "^=", - SHL_ASSIGN: "<<=", - SHR_ASSIGN: ">>=", - AND_NOT_ASSIGN: "&^=", - - LAND: "&&", - LOR: "||", - ARROW: "<-", - INC: "++", - DEC: "--", - - EQL: "==", - LSS: "<", - GTR: ">", - ASSIGN: "=", - NOT: "!", - - NEQ: "!=", - LEQ: "<=", - GEQ: ">=", - DEFINE: ":=", - ELLIPSIS: "...", - - LPAREN: "(", - LBRACK: "[", - LBRACE: "{", - COMMA: ",", - PERIOD: ".", - - RPAREN: ")", - RBRACK: "]", - RBRACE: "}", - SEMICOLON: ";", - COLON: ":", - - BREAK: "break", - CASE: "case", - CHAN: "chan", - CONST: "const", - CONTINUE: "continue", - - DEFAULT: "default", - DEFER: "defer", - ELSE: "else", - FALLTHROUGH: "fallthrough", - FOR: "for", - - FUNC: "func", - GO: "go", - GOTO: "goto", - IF: "if", - IMPORT: "import", - - INTERFACE: "interface", - MAP: "map", - PACKAGE: "package", - RANGE: "range", - RETURN: "return", - - SELECT: "select", - STRUCT: "struct", - SWITCH: "switch", - TYPE: "type", - VAR: "var", - - TILDE: "~", -} - -// String returns the string corresponding to the token tok. -// For operators, delimiters, and keywords the string is the actual -// token character sequence (e.g., for the token ADD, the string is -// "+"). For all other tokens the string corresponds to the token -// constant name (e.g. for the token IDENT, the string is "IDENT"). -func (tok Token) String() string { - s := "" - if 0 <= tok && tok < Token(len(tokens)) { - s = tokens[tok] - } - if s == "" { - s = "token(" + strconv.Itoa(int(tok)) + ")" - } - return s -} - -// A set of constants for precedence-based expression parsing. -// Non-operators have lowest precedence, followed by operators -// starting with precedence 1 up to unary operators. The highest -// precedence serves as "catch-all" precedence for selector, -// indexing, and other operator and delimiter tokens. -const ( - LowestPrec = 0 // non-operators - UnaryPrec = 6 - HighestPrec = 7 -) - -// Precedence returns the operator precedence of the binary -// operator op. If op is not a binary operator, the result -// is LowestPrecedence. -func (op Token) Precedence() int { - switch op { - case LOR: - return 1 - case LAND: - return 2 - case EQL, NEQ, LSS, LEQ, GTR, GEQ: - return 3 - case ADD, SUB, OR, XOR: - return 4 - case MUL, QUO, REM, SHL, SHR, AND, AND_NOT: - return 5 - } - return LowestPrec -} - -var keywords map[string]Token - -func init() { - keywords = make(map[string]Token) - for i := keyword_beg + 1; i < keyword_end; i++ { - keywords[tokens[i]] = i - } -} - -// Lookup maps an identifier to its keyword token or IDENT (if not a keyword). -func Lookup(ident string) Token { - if tok, is_keyword := keywords[ident]; is_keyword { - return tok - } - return IDENT -} - -// Predicates - -// IsLiteral returns true for tokens corresponding to identifiers -// and basic type literals; it returns false otherwise. -func (tok Token) IsLiteral() bool { return literal_beg < tok && tok < literal_end } - -// IsOperator returns true for tokens corresponding to operators and -// delimiters; it returns false otherwise. -func (tok Token) IsOperator() bool { - return (operator_beg < tok && tok < operator_end) || tok == TILDE -} - -// IsKeyword returns true for tokens corresponding to keywords; -// it returns false otherwise. -func (tok Token) IsKeyword() bool { return keyword_beg < tok && tok < keyword_end } - -// IsExported reports whether name starts with an upper-case letter. -func IsExported(name string) bool { - ch, _ := utf8.DecodeRuneInString(name) - return unicode.IsUpper(ch) -} - -// IsKeyword reports whether name is a Go keyword, such as "func" or "return". -func IsKeyword(name string) bool { - // TODO: opt: use a perfect hash function instead of a global map. - _, ok := keywords[name] - return ok -} - -// IsIdentifier reports whether name is a Go identifier, that is, a non-empty -// string made up of letters, digits, and underscores, where the first character -// is not a digit. Keywords are not identifiers. -func IsIdentifier(name string) bool { - for i, c := range name { - if !unicode.IsLetter(c) && c != '_' && (i == 0 || !unicode.IsDigit(c)) { - return false - } - } - return name != "" && !IsKeyword(name) -} diff --git a/internal/backport/go/token/token_test.go b/internal/backport/go/token/token_test.go deleted file mode 100644 index eff38cc928..0000000000 --- a/internal/backport/go/token/token_test.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2019 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 token - -import "testing" - -func TestIsIdentifier(t *testing.T) { - tests := []struct { - name string - in string - want bool - }{ - {"Empty", "", false}, - {"Space", " ", false}, - {"SpaceSuffix", "foo ", false}, - {"Number", "123", false}, - {"Keyword", "func", false}, - - {"LettersASCII", "foo", true}, - {"MixedASCII", "_bar123", true}, - {"UppercaseKeyword", "Func", true}, - {"LettersUnicode", "fóö", true}, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - if got := IsIdentifier(test.in); got != test.want { - t.Fatalf("IsIdentifier(%q) = %t, want %v", test.in, got, test.want) - } - }) - } -} diff --git a/internal/backport/html/template/attr.go b/internal/backport/html/template/attr.go deleted file mode 100644 index 22922e6038..0000000000 --- a/internal/backport/html/template/attr.go +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright 2011 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 template - -import ( - "strings" -) - -// attrTypeMap[n] describes the value of the given attribute. -// If an attribute affects (or can mask) the encoding or interpretation of -// other content, or affects the contents, idempotency, or credentials of a -// network message, then the value in this map is contentTypeUnsafe. -// This map is derived from HTML5, specifically -// https://www.w3.org/TR/html5/Overview.html#attributes-1 -// as well as "%URI"-typed attributes from -// https://www.w3.org/TR/html4/index/attributes.html -var attrTypeMap = map[string]contentType{ - "accept": contentTypePlain, - "accept-charset": contentTypeUnsafe, - "action": contentTypeURL, - "alt": contentTypePlain, - "archive": contentTypeURL, - "async": contentTypeUnsafe, - "autocomplete": contentTypePlain, - "autofocus": contentTypePlain, - "autoplay": contentTypePlain, - "background": contentTypeURL, - "border": contentTypePlain, - "checked": contentTypePlain, - "cite": contentTypeURL, - "challenge": contentTypeUnsafe, - "charset": contentTypeUnsafe, - "class": contentTypePlain, - "classid": contentTypeURL, - "codebase": contentTypeURL, - "cols": contentTypePlain, - "colspan": contentTypePlain, - "content": contentTypeUnsafe, - "contenteditable": contentTypePlain, - "contextmenu": contentTypePlain, - "controls": contentTypePlain, - "coords": contentTypePlain, - "crossorigin": contentTypeUnsafe, - "data": contentTypeURL, - "datetime": contentTypePlain, - "default": contentTypePlain, - "defer": contentTypeUnsafe, - "dir": contentTypePlain, - "dirname": contentTypePlain, - "disabled": contentTypePlain, - "draggable": contentTypePlain, - "dropzone": contentTypePlain, - "enctype": contentTypeUnsafe, - "for": contentTypePlain, - "form": contentTypeUnsafe, - "formaction": contentTypeURL, - "formenctype": contentTypeUnsafe, - "formmethod": contentTypeUnsafe, - "formnovalidate": contentTypeUnsafe, - "formtarget": contentTypePlain, - "headers": contentTypePlain, - "height": contentTypePlain, - "hidden": contentTypePlain, - "high": contentTypePlain, - "href": contentTypeURL, - "hreflang": contentTypePlain, - "http-equiv": contentTypeUnsafe, - "icon": contentTypeURL, - "id": contentTypePlain, - "ismap": contentTypePlain, - "keytype": contentTypeUnsafe, - "kind": contentTypePlain, - "label": contentTypePlain, - "lang": contentTypePlain, - "language": contentTypeUnsafe, - "list": contentTypePlain, - "longdesc": contentTypeURL, - "loop": contentTypePlain, - "low": contentTypePlain, - "manifest": contentTypeURL, - "max": contentTypePlain, - "maxlength": contentTypePlain, - "media": contentTypePlain, - "mediagroup": contentTypePlain, - "method": contentTypeUnsafe, - "min": contentTypePlain, - "multiple": contentTypePlain, - "name": contentTypePlain, - "novalidate": contentTypeUnsafe, - // Skip handler names from - // https://www.w3.org/TR/html5/webappapis.html#event-handlers-on-elements,-document-objects,-and-window-objects - // since we have special handling in attrType. - "open": contentTypePlain, - "optimum": contentTypePlain, - "pattern": contentTypeUnsafe, - "placeholder": contentTypePlain, - "poster": contentTypeURL, - "profile": contentTypeURL, - "preload": contentTypePlain, - "pubdate": contentTypePlain, - "radiogroup": contentTypePlain, - "readonly": contentTypePlain, - "rel": contentTypeUnsafe, - "required": contentTypePlain, - "reversed": contentTypePlain, - "rows": contentTypePlain, - "rowspan": contentTypePlain, - "sandbox": contentTypeUnsafe, - "spellcheck": contentTypePlain, - "scope": contentTypePlain, - "scoped": contentTypePlain, - "seamless": contentTypePlain, - "selected": contentTypePlain, - "shape": contentTypePlain, - "size": contentTypePlain, - "sizes": contentTypePlain, - "span": contentTypePlain, - "src": contentTypeURL, - "srcdoc": contentTypeHTML, - "srclang": contentTypePlain, - "srcset": contentTypeSrcset, - "start": contentTypePlain, - "step": contentTypePlain, - "style": contentTypeCSS, - "tabindex": contentTypePlain, - "target": contentTypePlain, - "title": contentTypePlain, - "type": contentTypeUnsafe, - "usemap": contentTypeURL, - "value": contentTypeUnsafe, - "width": contentTypePlain, - "wrap": contentTypePlain, - "xmlns": contentTypeURL, -} - -// attrType returns a conservative (upper-bound on authority) guess at the -// type of the lowercase named attribute. -func attrType(name string) contentType { - if strings.HasPrefix(name, "data-") { - // Strip data- so that custom attribute heuristics below are - // widely applied. - // Treat data-action as URL below. - name = name[5:] - } else if colon := strings.IndexRune(name, ':'); colon != -1 { - if name[:colon] == "xmlns" { - return contentTypeURL - } - // Treat svg:href and xlink:href as href below. - name = name[colon+1:] - } - if t, ok := attrTypeMap[name]; ok { - return t - } - // Treat partial event handler names as script. - if strings.HasPrefix(name, "on") { - return contentTypeJS - } - - // Heuristics to prevent "javascript:..." injection in custom - // data attributes and custom attributes like g:tweetUrl. - // https://www.w3.org/TR/html5/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes - // "Custom data attributes are intended to store custom data - // private to the page or application, for which there are no - // more appropriate attributes or elements." - // Developers seem to store URL content in data URLs that start - // or end with "URI" or "URL". - if strings.Contains(name, "src") || - strings.Contains(name, "uri") || - strings.Contains(name, "url") { - return contentTypeURL - } - return contentTypePlain -} diff --git a/internal/backport/html/template/attr_string.go b/internal/backport/html/template/attr_string.go deleted file mode 100644 index babe70c08b..0000000000 --- a/internal/backport/html/template/attr_string.go +++ /dev/null @@ -1,16 +0,0 @@ -// Code generated by "stringer -type attr"; DO NOT EDIT. - -package template - -import "strconv" - -const _attr_name = "attrNoneattrScriptattrScriptTypeattrStyleattrURLattrSrcset" - -var _attr_index = [...]uint8{0, 8, 18, 32, 41, 48, 58} - -func (i attr) String() string { - if i >= attr(len(_attr_index)-1) { - return "attr(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _attr_name[_attr_index[i]:_attr_index[i+1]] -} diff --git a/internal/backport/html/template/clone_test.go b/internal/backport/html/template/clone_test.go deleted file mode 100644 index 07e8ef39a1..0000000000 --- a/internal/backport/html/template/clone_test.go +++ /dev/null @@ -1,280 +0,0 @@ -// Copyright 2011 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 template - -import ( - "bytes" - "errors" - "fmt" - "io/ioutil" - "strings" - "sync" - "testing" - - "golang.org/x/website/internal/backport/text/template/parse" -) - -func TestAddParseTreeHTML(t *testing.T) { - root := Must(New("root").Parse(`{{define "a"}} {{.}} {{template "b"}} {{.}} ">{{end}}`)) - tree, err := parse.Parse("t", `{{define "b"}}0") - if err != nil { - t.Fatal(err) - } - if got, want := b.String(), ` 1>0 `; got != want { - t.Errorf("got %q want %q", got, want) - } -} - -func TestClone(t *testing.T) { - // The {{.}} will be executed with data "*/" in different contexts. - // In the t0 template, it will be in a text context. - // In the t1 template, it will be in a URL context. - // In the t2 template, it will be in a JavaScript context. - // In the t3 template, it will be in a CSS context. - const tmpl = `{{define "a"}}{{template "lhs"}}{{.}}{{template "rhs"}}{{end}}` - b := new(bytes.Buffer) - - // Create an incomplete template t0. - t0 := Must(New("t0").Parse(tmpl)) - - // Clone t0 as t1. - t1 := Must(t0.Clone()) - Must(t1.Parse(`{{define "lhs"}} {{end}}`)) - - // Execute t1. - b.Reset() - if err := t1.ExecuteTemplate(b, "a", "*/"); err != nil { - t.Fatal(err) - } - if got, want := b.String(), ` `; got != want { - t.Errorf("t1: got %q want %q", got, want) - } - - // Clone t0 as t2. - t2 := Must(t0.Clone()) - Must(t2.Parse(`{{define "lhs"}}

{{end}}`)) - - // Execute t2. - b.Reset() - if err := t2.ExecuteTemplate(b, "a", "*/"); err != nil { - t.Fatal(err) - } - if got, want := b.String(), `

`; got != want { - t.Errorf("t2: got %q want %q", got, want) - } - - // Clone t0 as t3, but do not execute t3 yet. - t3 := Must(t0.Clone()) - Must(t3.Parse(`{{define "lhs"}} {{end}}`)) - - // Complete t0. - Must(t0.Parse(`{{define "lhs"}} ( {{end}}`)) - Must(t0.Parse(`{{define "rhs"}} ) {{end}}`)) - - // Clone t0 as t4. Redefining the "lhs" template should not fail. - t4 := Must(t0.Clone()) - if _, err := t4.Parse(`{{define "lhs"}} OK {{end}}`); err != nil { - t.Errorf(`redefine "lhs": got err %v want nil`, err) - } - // Cloning t1 should fail as it has been executed. - if _, err := t1.Clone(); err == nil { - t.Error("cloning t1: got nil err want non-nil") - } - // Redefining the "lhs" template in t1 should fail as it has been executed. - if _, err := t1.Parse(`{{define "lhs"}} OK {{end}}`); err == nil { - t.Error(`redefine "lhs": got nil err want non-nil`) - } - - // Execute t0. - b.Reset() - if err := t0.ExecuteTemplate(b, "a", "*/"); err != nil { - t.Fatal(err) - } - if got, want := b.String(), ` ( <i>*/ ) `; got != want { - t.Errorf("t0: got %q want %q", got, want) - } - - // Clone t0. This should fail, as t0 has already executed. - if _, err := t0.Clone(); err == nil { - t.Error(`t0.Clone(): got nil err want non-nil`) - } - - // Similarly, cloning sub-templates should fail. - if _, err := t0.Lookup("a").Clone(); err == nil { - t.Error(`t0.Lookup("a").Clone(): got nil err want non-nil`) - } - if _, err := t0.Lookup("lhs").Clone(); err == nil { - t.Error(`t0.Lookup("lhs").Clone(): got nil err want non-nil`) - } - - // Execute t3. - b.Reset() - if err := t3.ExecuteTemplate(b, "a", "*/"); err != nil { - t.Fatal(err) - } - if got, want := b.String(), ` `; got != want { - t.Errorf("t3: got %q want %q", got, want) - } -} - -func TestTemplates(t *testing.T) { - names := []string{"t0", "a", "lhs", "rhs"} - // Some template definitions borrowed from TestClone. - const tmpl = ` - {{define "a"}}{{template "lhs"}}{{.}}{{template "rhs"}}{{end}} - {{define "lhs"}} {{end}}` - t0 := Must(New("t0").Parse(tmpl)) - templates := t0.Templates() - if len(templates) != len(names) { - t.Errorf("expected %d templates; got %d", len(names), len(templates)) - } - for _, name := range names { - found := false - for _, tmpl := range templates { - if name == tmpl.text.Name() { - found = true - break - } - } - if !found { - t.Error("could not find template", name) - } - } -} - -// This used to crash; https://golang.org/issue/3281 -func TestCloneCrash(t *testing.T) { - t1 := New("all") - Must(t1.New("t1").Parse(`{{define "foo"}}foo{{end}}`)) - t1.Clone() -} - -// Ensure that this guarantee from the docs is upheld: -// "Further calls to Parse in the copy will add templates -// to the copy but not to the original." -func TestCloneThenParse(t *testing.T) { - t0 := Must(New("t0").Parse(`{{define "a"}}{{template "embedded"}}{{end}}`)) - t1 := Must(t0.Clone()) - Must(t1.Parse(`{{define "embedded"}}t1{{end}}`)) - if len(t0.Templates())+1 != len(t1.Templates()) { - t.Error("adding a template to a clone added it to the original") - } - // double check that the embedded template isn't available in the original - err := t0.ExecuteTemplate(ioutil.Discard, "a", nil) - if err == nil { - t.Error("expected 'no such template' error") - } -} - -// https://golang.org/issue/5980 -func TestFuncMapWorksAfterClone(t *testing.T) { - funcs := FuncMap{"customFunc": func() (string, error) { - return "", errors.New("issue5980") - }} - - // get the expected error output (no clone) - uncloned := Must(New("").Funcs(funcs).Parse("{{customFunc}}")) - wantErr := uncloned.Execute(ioutil.Discard, nil) - - // toClone must be the same as uncloned. It has to be recreated from scratch, - // since cloning cannot occur after execution. - toClone := Must(New("").Funcs(funcs).Parse("{{customFunc}}")) - cloned := Must(toClone.Clone()) - gotErr := cloned.Execute(ioutil.Discard, nil) - - if wantErr.Error() != gotErr.Error() { - t.Errorf("clone error message mismatch want %q got %q", wantErr, gotErr) - } -} - -// https://golang.org/issue/16101 -func TestTemplateCloneExecuteRace(t *testing.T) { - const ( - input = `{{block "a" .}}a{{end}}{{block "b" .}}b{{end}}` - overlay = `{{define "b"}}A{{end}}` - ) - outer := Must(New("outer").Parse(input)) - tmpl := Must(Must(outer.Clone()).Parse(overlay)) - - var wg sync.WaitGroup - for i := 0; i < 10; i++ { - wg.Add(1) - go func() { - defer wg.Done() - for i := 0; i < 100; i++ { - if err := tmpl.Execute(ioutil.Discard, "data"); err != nil { - panic(err) - } - } - }() - } - wg.Wait() -} - -func TestTemplateCloneLookup(t *testing.T) { - // Template.escape makes an assumption that the template associated - // with t.Name() is t. Check that this holds. - tmpl := Must(New("x").Parse("a")) - tmpl = Must(tmpl.Clone()) - if tmpl.Lookup(tmpl.Name()) != tmpl { - t.Error("after Clone, tmpl.Lookup(tmpl.Name()) != tmpl") - } -} - -func TestCloneGrowth(t *testing.T) { - tmpl := Must(New("root").Parse(`{{block "B". }}Arg{{end}}`)) - tmpl = Must(tmpl.Clone()) - Must(tmpl.Parse(`{{define "B"}}Text{{end}}`)) - for i := 0; i < 10; i++ { - tmpl.Execute(ioutil.Discard, nil) - } - if len(tmpl.DefinedTemplates()) > 200 { - t.Fatalf("too many templates: %v", len(tmpl.DefinedTemplates())) - } -} - -// https://golang.org/issue/17735 -func TestCloneRedefinedName(t *testing.T) { - const base = ` -{{ define "a" -}}{{ template "b" . -}}{{ end -}} -{{ define "b" }}{{ end -}} -` - const page = `{{ template "a" . }}` - - t1 := Must(New("a").Parse(base)) - - for i := 0; i < 2; i++ { - t2 := Must(t1.Clone()) - t2 = Must(t2.New(fmt.Sprintf("%d", i)).Parse(page)) - err := t2.Execute(ioutil.Discard, nil) - if err != nil { - t.Fatal(err) - } - } -} - -// Issue 24791. -func TestClonePipe(t *testing.T) { - a := Must(New("a").Parse(`{{define "a"}}{{range $v := .A}}{{$v}}{{end}}{{end}}`)) - data := struct{ A []string }{A: []string{"hi"}} - b := Must(a.Clone()) - var buf strings.Builder - if err := b.Execute(&buf, &data); err != nil { - t.Fatal(err) - } - if got, want := buf.String(), "hi"; got != want { - t.Errorf("got %q want %q", got, want) - } -} diff --git a/internal/backport/html/template/content.go b/internal/backport/html/template/content.go deleted file mode 100644 index 9cf3153a88..0000000000 --- a/internal/backport/html/template/content.go +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright 2011 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 template - -import ( - "fmt" - "reflect" - - stdtemplate "html/template" -) - -// Strings of content from a trusted source. -type ( - // CSS encapsulates known safe content that matches any of: - // 1. The CSS3 stylesheet production, such as `p { color: purple }`. - // 2. The CSS3 rule production, such as `a[href=~"https:"].foo#bar`. - // 3. CSS3 declaration productions, such as `color: red; margin: 2px`. - // 4. The CSS3 value production, such as `rgba(0, 0, 255, 127)`. - // See https://www.w3.org/TR/css3-syntax/#parsing and - // https://web.archive.org/web/20090211114933/http://w3.org/TR/css3-syntax#style - // - // Use of this type presents a security risk: - // the encapsulated content should come from a trusted source, - // as it will be included verbatim in the template output. - CSS = stdtemplate.CSS - - // HTML encapsulates a known safe HTML document fragment. - // It should not be used for HTML from a third-party, or HTML with - // unclosed tags or comments. The outputs of a sound HTML sanitizer - // and a template escaped by this package are fine for use with HTML. - // - // Use of this type presents a security risk: - // the encapsulated content should come from a trusted source, - // as it will be included verbatim in the template output. - HTML = stdtemplate.HTML - - // HTMLAttr encapsulates an HTML attribute from a trusted source, - // for example, ` dir="ltr"`. - // - // Use of this type presents a security risk: - // the encapsulated content should come from a trusted source, - // as it will be included verbatim in the template output. - HTMLAttr = stdtemplate.HTMLAttr - - // JS encapsulates a known safe EcmaScript5 Expression, for example, - // `(x + y * z())`. - // Template authors are responsible for ensuring that typed expressions - // do not break the intended precedence and that there is no - // statement/expression ambiguity as when passing an expression like - // "{ foo: bar() }\n['foo']()", which is both a valid Expression and a - // valid Program with a very different meaning. - // - // Use of this type presents a security risk: - // the encapsulated content should come from a trusted source, - // as it will be included verbatim in the template output. - // - // Using JS to include valid but untrusted JSON is not safe. - // A safe alternative is to parse the JSON with json.Unmarshal and then - // pass the resultant object into the template, where it will be - // converted to sanitized JSON when presented in a JavaScript context. - JS = stdtemplate.JS - - // JSStr encapsulates a sequence of characters meant to be embedded - // between quotes in a JavaScript expression. - // The string must match a series of StringCharacters: - // StringCharacter :: SourceCharacter but not `\` or LineTerminator - // | EscapeSequence - // Note that LineContinuations are not allowed. - // JSStr("foo\\nbar") is fine, but JSStr("foo\\\nbar") is not. - // - // Use of this type presents a security risk: - // the encapsulated content should come from a trusted source, - // as it will be included verbatim in the template output. - JSStr = stdtemplate.JSStr - - // URL encapsulates a known safe URL or URL substring (see RFC 3986). - // A URL like `javascript:checkThatFormNotEditedBeforeLeavingPage()` - // from a trusted source should go in the page, but by default dynamic - // `javascript:` URLs are filtered out since they are a frequently - // exploited injection vector. - // - // Use of this type presents a security risk: - // the encapsulated content should come from a trusted source, - // as it will be included verbatim in the template output. - URL = stdtemplate.URL - - // Srcset encapsulates a known safe srcset attribute - // (see https://w3c.github.io/html/semantics-embedded-content.html#element-attrdef-img-srcset). - // - // Use of this type presents a security risk: - // the encapsulated content should come from a trusted source, - // as it will be included verbatim in the template output. - Srcset = stdtemplate.Srcset -) - -type contentType uint8 - -const ( - contentTypePlain contentType = iota - contentTypeCSS - contentTypeHTML - contentTypeHTMLAttr - contentTypeJS - contentTypeJSStr - contentTypeURL - contentTypeSrcset - // contentTypeUnsafe is used in attr.go for values that affect how - // embedded content and network messages are formed, vetted, - // or interpreted; or which credentials network messages carry. - contentTypeUnsafe -) - -// indirect returns the value, after dereferencing as many times -// as necessary to reach the base type (or nil). -func indirect(a interface{}) interface{} { - if a == nil { - return nil - } - if t := reflect.TypeOf(a); t.Kind() != reflect.Ptr { - // Avoid creating a reflect.Value if it's not a pointer. - return a - } - v := reflect.ValueOf(a) - for v.Kind() == reflect.Ptr && !v.IsNil() { - v = v.Elem() - } - return v.Interface() -} - -var ( - errorType = reflect.TypeOf((*error)(nil)).Elem() - fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem() -) - -// indirectToStringerOrError returns the value, after dereferencing as many times -// as necessary to reach the base type (or nil) or an implementation of fmt.Stringer -// or error, -func indirectToStringerOrError(a interface{}) interface{} { - if a == nil { - return nil - } - v := reflect.ValueOf(a) - for !v.Type().Implements(fmtStringerType) && !v.Type().Implements(errorType) && v.Kind() == reflect.Ptr && !v.IsNil() { - v = v.Elem() - } - return v.Interface() -} - -// stringify converts its arguments to a string and the type of the content. -// All pointers are dereferenced, as in the text/template package. -func stringify(args ...interface{}) (string, contentType) { - if len(args) == 1 { - switch s := indirect(args[0]).(type) { - case string: - return s, contentTypePlain - case CSS: - return string(s), contentTypeCSS - case HTML: - return string(s), contentTypeHTML - case HTMLAttr: - return string(s), contentTypeHTMLAttr - case JS: - return string(s), contentTypeJS - case JSStr: - return string(s), contentTypeJSStr - case URL: - return string(s), contentTypeURL - case Srcset: - return string(s), contentTypeSrcset - } - } - i := 0 - for _, arg := range args { - // We skip untyped nil arguments for backward compatibility. - // Without this they would be output as , escaped. - // See issue 25875. - if arg == nil { - continue - } - - args[i] = indirectToStringerOrError(arg) - i++ - } - return fmt.Sprint(args[:i]...), contentTypePlain -} diff --git a/internal/backport/html/template/content_test.go b/internal/backport/html/template/content_test.go deleted file mode 100644 index e6f47a27b9..0000000000 --- a/internal/backport/html/template/content_test.go +++ /dev/null @@ -1,458 +0,0 @@ -// Copyright 2011 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 template - -import ( - "bytes" - "fmt" - "strings" - "testing" -) - -func TestTypedContent(t *testing.T) { - data := []interface{}{ - ` "foo%" O'Reilly &bar;`, - CSS(`a[href =~ "//example.com"]#foo`), - HTML(`Hello, World &tc!`), - HTMLAttr(` dir="ltr"`), - JS(`c && alert("Hello, World!");`), - JSStr(`Hello, World & O'Reilly\u0021`), - URL(`greeting=H%69,&addressee=(World)`), - Srcset(`greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`), - URL(`,foo/,`), - } - - // For each content sensitive escaper, see how it does on - // each of the typed strings above. - tests := []struct { - // A template containing a single {{.}}. - input string - want []string - }{ - { - ``, - []string{ - `ZgotmplZ`, - // Allowed but not escaped. - `a[href =~ "//example.com"]#foo`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - }, - }, - { - `
`, - []string{ - `ZgotmplZ`, - // Allowed and HTML escaped. - `a[href =~ "//example.com"]#foo`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - }, - }, - { - `{{.}}`, - []string{ - `<b> "foo%" O'Reilly &bar;`, - `a[href =~ "//example.com"]#foo`, - // Not escaped. - `Hello, World &tc!`, - ` dir="ltr"`, - `c && alert("Hello, World!");`, - `Hello, World & O'Reilly\u0021`, - `greeting=H%69,&addressee=(World)`, - `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`, - `,foo/,`, - }, - }, - { - ``, - []string{ - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - // Allowed and HTML escaped. - ` dir="ltr"`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - }, - }, - { - ``, - []string{ - `<b> "foo%" O'Reilly &bar;`, - `a[href =~ "//example.com"]#foo`, - // Tags stripped, spaces escaped, entity not re-escaped. - `Hello, World &tc!`, - ` dir="ltr"`, - `c && alert("Hello, World!");`, - `Hello, World & O'Reilly\u0021`, - `greeting=H%69,&addressee=(World)`, - `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`, - `,foo/,`, - }, - }, - { - ``, - []string{ - `<b> "foo%" O'Reilly &bar;`, - `a[href =~ "//example.com"]#foo`, - // Tags stripped, entity not re-escaped. - `Hello, World &tc!`, - ` dir="ltr"`, - `c && alert("Hello, World!");`, - `Hello, World & O'Reilly\u0021`, - `greeting=H%69,&addressee=(World)`, - `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`, - `,foo/,`, - }, - }, - { - ``, - []string{ - `<b> "foo%" O'Reilly &bar;`, - `a[href =~ "//example.com"]#foo`, - // Angle brackets escaped to prevent injection of close tags, entity not re-escaped. - `Hello, <b>World</b> &tc!`, - ` dir="ltr"`, - `c && alert("Hello, World!");`, - `Hello, World & O'Reilly\u0021`, - `greeting=H%69,&addressee=(World)`, - `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`, - `,foo/,`, - }, - }, - { - ``, - []string{ - `"\u003cb\u003e \"foo%\" O'Reilly \u0026bar;"`, - `"a[href =~ \"//example.com\"]#foo"`, - `"Hello, \u003cb\u003eWorld\u003c/b\u003e \u0026amp;tc!"`, - `" dir=\"ltr\""`, - // Not escaped. - `c && alert("Hello, World!");`, - // Escape sequence not over-escaped. - `"Hello, World & O'Reilly\u0021"`, - `"greeting=H%69,\u0026addressee=(World)"`, - `"greeting=H%69,\u0026addressee=(World) 2x, https://golang.org/favicon.ico 500.5w"`, - `",foo/,"`, - }, - }, - { - `