...

Source file src/go/format/format.go

Documentation: go/format

		 1  // Copyright 2012 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 implements standard formatting of Go source.
		 6  //
		 7  // Note that formatting of Go source code changes over time, so tools relying on
		 8  // consistent formatting should execute a specific version of the gofmt binary
		 9  // instead of using this package. That way, the formatting will be stable, and
		10  // the tools won't need to be recompiled each time gofmt changes.
		11  //
		12  // For example, pre-submit checks that use this package directly would behave
		13  // differently depending on what Go version each developer uses, causing the
		14  // check to be inherently fragile.
		15  package format
		16  
		17  import (
		18  	"bytes"
		19  	"fmt"
		20  	"go/ast"
		21  	"go/parser"
		22  	"go/printer"
		23  	"go/token"
		24  	"io"
		25  )
		26  
		27  // Keep these in sync with cmd/gofmt/gofmt.go.
		28  const (
		29  	tabWidth		= 8
		30  	printerMode = printer.UseSpaces | printer.TabIndent | printerNormalizeNumbers
		31  
		32  	// printerNormalizeNumbers means to canonicalize number literal prefixes
		33  	// and exponents while printing. See https://golang.org/doc/go1.13#gofmt.
		34  	//
		35  	// This value is defined in go/printer specifically for go/format and cmd/gofmt.
		36  	printerNormalizeNumbers = 1 << 30
		37  )
		38  
		39  var config = printer.Config{Mode: printerMode, Tabwidth: tabWidth}
		40  
		41  const parserMode = parser.ParseComments
		42  
		43  // Node formats node in canonical gofmt style and writes the result to dst.
		44  //
		45  // The node type must be *ast.File, *printer.CommentedNode, []ast.Decl,
		46  // []ast.Stmt, or assignment-compatible to ast.Expr, ast.Decl, ast.Spec,
		47  // or ast.Stmt. Node does not modify node. Imports are not sorted for
		48  // nodes representing partial source files (for instance, if the node is
		49  // not an *ast.File or a *printer.CommentedNode not wrapping an *ast.File).
		50  //
		51  // The function may return early (before the entire result is written)
		52  // and return a formatting error, for instance due to an incorrect AST.
		53  //
		54  func Node(dst io.Writer, fset *token.FileSet, node interface{}) error {
		55  	// Determine if we have a complete source file (file != nil).
		56  	var file *ast.File
		57  	var cnode *printer.CommentedNode
		58  	switch n := node.(type) {
		59  	case *ast.File:
		60  		file = n
		61  	case *printer.CommentedNode:
		62  		if f, ok := n.Node.(*ast.File); ok {
		63  			file = f
		64  			cnode = n
		65  		}
		66  	}
		67  
		68  	// Sort imports if necessary.
		69  	if file != nil && hasUnsortedImports(file) {
		70  		// Make a copy of the AST because ast.SortImports is destructive.
		71  		// TODO(gri) Do this more efficiently.
		72  		var buf bytes.Buffer
		73  		err := config.Fprint(&buf, fset, file)
		74  		if err != nil {
		75  			return err
		76  		}
		77  		file, err = parser.ParseFile(fset, "", buf.Bytes(), parserMode)
		78  		if err != nil {
		79  			// We should never get here. If we do, provide good diagnostic.
		80  			return fmt.Errorf("format.Node internal error (%s)", err)
		81  		}
		82  		ast.SortImports(fset, file)
		83  
		84  		// Use new file with sorted imports.
		85  		node = file
		86  		if cnode != nil {
		87  			node = &printer.CommentedNode{Node: file, Comments: cnode.Comments}
		88  		}
		89  	}
		90  
		91  	return config.Fprint(dst, fset, node)
		92  }
		93  
		94  // Source formats src in canonical gofmt style and returns the result
		95  // or an (I/O or syntax) error. src is expected to be a syntactically
		96  // correct Go source file, or a list of Go declarations or statements.
		97  //
		98  // If src is a partial source file, the leading and trailing space of src
		99  // is applied to the result (such that it has the same leading and trailing
	 100  // space as src), and the result is indented by the same amount as the first
	 101  // line of src containing code. Imports are not sorted for partial source files.
	 102  //
	 103  func Source(src []byte) ([]byte, error) {
	 104  	fset := token.NewFileSet()
	 105  	file, sourceAdj, indentAdj, err := parse(fset, "", src, true)
	 106  	if err != nil {
	 107  		return nil, err
	 108  	}
	 109  
	 110  	if sourceAdj == nil {
	 111  		// Complete source file.
	 112  		// TODO(gri) consider doing this always.
	 113  		ast.SortImports(fset, file)
	 114  	}
	 115  
	 116  	return format(fset, file, sourceAdj, indentAdj, src, config)
	 117  }
	 118  
	 119  func hasUnsortedImports(file *ast.File) bool {
	 120  	for _, d := range file.Decls {
	 121  		d, ok := d.(*ast.GenDecl)
	 122  		if !ok || d.Tok != token.IMPORT {
	 123  			// Not an import declaration, so we're done.
	 124  			// Imports are always first.
	 125  			return false
	 126  		}
	 127  		if d.Lparen.IsValid() {
	 128  			// For now assume all grouped imports are unsorted.
	 129  			// TODO(gri) Should check if they are sorted already.
	 130  			return true
	 131  		}
	 132  		// Ungrouped imports are sorted by default.
	 133  	}
	 134  	return false
	 135  }
	 136  

View as plain text