github.com/rsc/go@v0.0.0-20150416155037-e040fd465409/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.
    62  	fsrc := append(append([]byte("package p; func _() {"), src...), '\n', '}')
    63  	file, err = parser.ParseFile(fset, filename, fsrc, parserMode)
    64  	if err == nil {
    65  		sourceAdj = func(src []byte, indent int) []byte {
    66  			// Cap adjusted indent to zero.
    67  			if indent < 0 {
    68  				indent = 0
    69  			}
    70  			// Remove the wrapping.
    71  			// Gofmt has turned the ; into a \n\n.
    72  			// There will be two non-blank lines with indent, hence 2*indent.
    73  			src = src[2*indent+len("package p\n\nfunc _() {"):]
    74  			src = src[:len(src)-(indent+len("\n}\n"))]
    75  			return bytes.TrimSpace(src)
    76  		}
    77  		// Gofmt has also indented the function body one level.
    78  		// Adjust that with indentAdj.
    79  		indentAdj = -1
    80  	}
    81  
    82  	// Succeeded, or out of options.
    83  	return
    84  }
    85  
    86  // Format formats the given package file originally obtained from src
    87  // and adjusts the result based on the original source via sourceAdj
    88  // and indentAdj.
    89  func Format(
    90  	fset *token.FileSet,
    91  	file *ast.File,
    92  	sourceAdj func(src []byte, indent int) []byte,
    93  	indentAdj int,
    94  	src []byte,
    95  	cfg printer.Config,
    96  ) ([]byte, error) {
    97  	if sourceAdj == nil {
    98  		// Complete source file.
    99  		var buf bytes.Buffer
   100  		err := cfg.Fprint(&buf, fset, file)
   101  		if err != nil {
   102  			return nil, err
   103  		}
   104  		return buf.Bytes(), nil
   105  	}
   106  
   107  	// Partial source file.
   108  	// Determine and prepend leading space.
   109  	i, j := 0, 0
   110  	for j < len(src) && IsSpace(src[j]) {
   111  		if src[j] == '\n' {
   112  			i = j + 1 // byte offset of last line in leading space
   113  		}
   114  		j++
   115  	}
   116  	var res []byte
   117  	res = append(res, src[:i]...)
   118  
   119  	// Determine and prepend indentation of first code line.
   120  	// Spaces are ignored unless there are no tabs,
   121  	// in which case spaces count as one tab.
   122  	indent := 0
   123  	hasSpace := false
   124  	for _, b := range src[i:j] {
   125  		switch b {
   126  		case ' ':
   127  			hasSpace = true
   128  		case '\t':
   129  			indent++
   130  		}
   131  	}
   132  	if indent == 0 && hasSpace {
   133  		indent = 1
   134  	}
   135  	for i := 0; i < indent; i++ {
   136  		res = append(res, '\t')
   137  	}
   138  
   139  	// Format the source.
   140  	// Write it without any leading and trailing space.
   141  	cfg.Indent = indent + indentAdj
   142  	var buf bytes.Buffer
   143  	err := cfg.Fprint(&buf, fset, file)
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  	res = append(res, sourceAdj(buf.Bytes(), cfg.Indent)...)
   148  
   149  	// Determine and append trailing space.
   150  	i = len(src)
   151  	for i > 0 && IsSpace(src[i-1]) {
   152  		i--
   153  	}
   154  	return append(res, src[i:]...), nil
   155  }
   156  
   157  // IsSpace reports whether the byte is a space character.
   158  // IsSpace defines a space as being among the following bytes: ' ', '\t', '\n' and '\r'.
   159  func IsSpace(b byte) bool {
   160  	return b == ' ' || b == '\t' || b == '\n' || b == '\r'
   161  }