Home | History | Annotate | Download | only in format
      1 // Copyright 2015 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package format
      6 
      7 import (
      8 	"bytes"
      9 	"go/ast"
     10 	"go/parser"
     11 	"go/printer"
     12 	"go/token"
     13 	"strings"
     14 )
     15 
     16 const parserMode = parser.ParseComments
     17 
     18 // Parse parses src, which was read from the named file,
     19 // as a Go source file, declaration, or statement list.
     20 func Parse(fset *token.FileSet, filename string, src []byte, fragmentOk bool) (
     21 	file *ast.File,
     22 	sourceAdj func(src []byte, indent int) []byte,
     23 	indentAdj int,
     24 	err error,
     25 ) {
     26 	// Try as whole source file.
     27 	file, err = parser.ParseFile(fset, filename, src, parserMode)
     28 	// If there's no error, return.  If the error is that the source file didn't begin with a
     29 	// package line and source fragments are ok, fall through to
     30 	// try as a source fragment.  Stop and return on any other error.
     31 	if err == nil || !fragmentOk || !strings.Contains(err.Error(), "expected 'package'") {
     32 		return
     33 	}
     34 
     35 	// If this is a declaration list, make it a source file
     36 	// by inserting a package clause.
     37 	// Insert using a ;, not a newline, so that the line numbers
     38 	// in psrc match the ones in src.
     39 	psrc := append([]byte("package p;"), src...)
     40 	file, err = parser.ParseFile(fset, filename, psrc, parserMode)
     41 	if err == nil {
     42 		sourceAdj = func(src []byte, indent int) []byte {
     43 			// Remove the package clause.
     44 			// Gofmt has turned the ; into a \n.
     45 			src = src[indent+len("package p\n"):]
     46 			return bytes.TrimSpace(src)
     47 		}
     48 		return
     49 	}
     50 	// If the error is that the source file didn't begin with a
     51 	// declaration, fall through to try as a statement list.
     52 	// Stop and return on any other error.
     53 	if !strings.Contains(err.Error(), "expected declaration") {
     54 		return
     55 	}
     56 
     57 	// If this is a statement list, make it a source file
     58 	// by inserting a package clause and turning the list
     59 	// into a function body.  This handles expressions too.
     60 	// Insert using a ;, not a newline, so that the line numbers
     61 	// in fsrc match the ones in src. Add an extra '\n' before the '}'
     62 	// to make sure comments are flushed before the '}'.
     63 	fsrc := append(append([]byte("package p; func _() {"), src...), '\n', '\n', '}')
     64 	file, err = parser.ParseFile(fset, filename, fsrc, parserMode)
     65 	if err == nil {
     66 		sourceAdj = func(src []byte, indent int) []byte {
     67 			// Cap adjusted indent to zero.
     68 			if indent < 0 {
     69 				indent = 0
     70 			}
     71 			// Remove the wrapping.
     72 			// Gofmt has turned the ; into a \n\n.
     73 			// There will be two non-blank lines with indent, hence 2*indent.
     74 			src = src[2*indent+len("package p\n\nfunc _() {"):]
     75 			// Remove only the "}\n" suffix: remaining whitespaces will be trimmed anyway
     76 			src = src[:len(src)-len("}\n")]
     77 			return bytes.TrimSpace(src)
     78 		}
     79 		// Gofmt has also indented the function body one level.
     80 		// Adjust that with indentAdj.
     81 		indentAdj = -1
     82 	}
     83 
     84 	// Succeeded, or out of options.
     85 	return
     86 }
     87 
     88 // Format formats the given package file originally obtained from src
     89 // and adjusts the result based on the original source via sourceAdj
     90 // and indentAdj.
     91 func Format(
     92 	fset *token.FileSet,
     93 	file *ast.File,
     94 	sourceAdj func(src []byte, indent int) []byte,
     95 	indentAdj int,
     96 	src []byte,
     97 	cfg printer.Config,
     98 ) ([]byte, error) {
     99 	if sourceAdj == nil {
    100 		// Complete source file.
    101 		var buf bytes.Buffer
    102 		err := cfg.Fprint(&buf, fset, file)
    103 		if err != nil {
    104 			return nil, err
    105 		}
    106 		return buf.Bytes(), nil
    107 	}
    108 
    109 	// Partial source file.
    110 	// Determine and prepend leading space.
    111 	i, j := 0, 0
    112 	for j < len(src) && IsSpace(src[j]) {
    113 		if src[j] == '\n' {
    114 			i = j + 1 // byte offset of last line in leading space
    115 		}
    116 		j++
    117 	}
    118 	var res []byte
    119 	res = append(res, src[:i]...)
    120 
    121 	// Determine and prepend indentation of first code line.
    122 	// Spaces are ignored unless there are no tabs,
    123 	// in which case spaces count as one tab.
    124 	indent := 0
    125 	hasSpace := false
    126 	for _, b := range src[i:j] {
    127 		switch b {
    128 		case ' ':
    129 			hasSpace = true
    130 		case '\t':
    131 			indent++
    132 		}
    133 	}
    134 	if indent == 0 && hasSpace {
    135 		indent = 1
    136 	}
    137 	for i := 0; i < indent; i++ {
    138 		res = append(res, '\t')
    139 	}
    140 
    141 	// Format the source.
    142 	// Write it without any leading and trailing space.
    143 	cfg.Indent = indent + indentAdj
    144 	var buf bytes.Buffer
    145 	err := cfg.Fprint(&buf, fset, file)
    146 	if err != nil {
    147 		return nil, err
    148 	}
    149 	res = append(res, sourceAdj(buf.Bytes(), cfg.Indent)...)
    150 
    151 	// Determine and append trailing space.
    152 	i = len(src)
    153 	for i > 0 && IsSpace(src[i-1]) {
    154 		i--
    155 	}
    156 	return append(res, src[i:]...), nil
    157 }
    158 
    159 // IsSpace reports whether the byte is a space character.
    160 // IsSpace defines a space as being among the following bytes: ' ', '\t', '\n' and '\r'.
    161 func IsSpace(b byte) bool {
    162 	return b == ' ' || b == '\t' || b == '\n' || b == '\r'
    163 }
    164