123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- // 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/go/format/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 main
- import (
- "bytes"
- "go/ast"
- "go/parser"
- "go/printer"
- "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'
- }
|