aboutsummaryrefslogtreecommitdiffstats
path: root/gcc-4.8.1/libgo/go/go/format/format.go
blob: 65b0e4e4b79b9b37d6116f6e5a452935a158645e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
// 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.
package format

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/parser"
	"go/printer"
	"go/token"
	"io"
	"strings"
)

var config = printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 8}

// 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 (i.e., 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(), parser.ParseComments)
		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 writes the result to dst
// or returns 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 formatted src 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()
	node, err := parse(fset, src)
	if err != nil {
		return nil, err
	}

	var buf bytes.Buffer
	if file, ok := node.(*ast.File); ok {
		// Complete source file.
		ast.SortImports(fset, file)
		err := config.Fprint(&buf, fset, file)
		if err != nil {
			return nil, err
		}

	} else {
		// 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 // index of last line in leading space
			}
			j++
		}
		buf.Write(src[:i])

		// Determine 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
		}

		// Format the source.
		cfg := config
		cfg.Indent = indent
		err := cfg.Fprint(&buf, fset, node)
		if err != nil {
			return nil, err
		}

		// Determine and append trailing space.
		i = len(src)
		for i > 0 && isSpace(src[i-1]) {
			i--
		}
		buf.Write(src[i:])
	}

	return buf.Bytes(), nil
}

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
}

func isSpace(b byte) bool {
	return b == ' ' || b == '\t' || b == '\n' || b == '\r'
}

func parse(fset *token.FileSet, src []byte) (interface{}, error) {
	// Try as a complete source file.
	file, err := parser.ParseFile(fset, "", src, parser.ParseComments)
	if err == nil {
		return file, nil
	}
	// If the source is missing a package clause, try as a source fragment; otherwise fail.
	if !strings.Contains(err.Error(), "expected 'package'") {
		return nil, err
	}

	// Try as a declaration list by prepending a package clause in front of src.
	// Use ';' not '\n' to keep line numbers intact.
	psrc := append([]byte("package p;"), src...)
	file, err = parser.ParseFile(fset, "", psrc, parser.ParseComments)
	if err == nil {
		return file.Decls, nil
	}
	// If the source is missing a declaration, try as a statement list; otherwise fail.
	if !strings.Contains(err.Error(), "expected declaration") {
		return nil, err
	}

	// Try as statement list by wrapping a function around src.
	fsrc := append(append([]byte("package p; func _() {"), src...), '}')
	file, err = parser.ParseFile(fset, "", fsrc, parser.ParseComments)
	if err == nil {
		return file.Decls[0].(*ast.FuncDecl).Body.List, nil
	}

	// Failed, and out of options.
	return nil, err
}