github.com/huandu/go@v0.0.0-20151114150818-04e615e41150/src/internal/format/format.go (about)

     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  }