mirror of
https://github.com/google/go-jsonnet.git
synced 2025-08-08 07:17:12 +02:00
202 lines
5.8 KiB
Go
202 lines
5.8 KiB
Go
/*
|
|
Copyright 2019 Google Inc. All rights reserved.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
// Package formatter provides API for producing pretty-printed source
|
|
// from AST.
|
|
package formatter
|
|
|
|
import (
|
|
"github.com/google/go-jsonnet/ast"
|
|
"github.com/google/go-jsonnet/internal/parser"
|
|
"github.com/google/go-jsonnet/internal/pass"
|
|
)
|
|
|
|
// StringStyle controls how the reformatter rewrites string literals.
|
|
// Strings that contain a ' or a " use the optimal syntax to avoid escaping
|
|
// those characters.
|
|
type StringStyle int
|
|
|
|
const (
|
|
// StringStyleDouble means "this".
|
|
StringStyleDouble StringStyle = iota
|
|
// StringStyleSingle means 'this'.
|
|
StringStyleSingle
|
|
// StringStyleLeave means strings are left how they were found.
|
|
StringStyleLeave
|
|
)
|
|
|
|
// CommentStyle controls how the reformatter rewrites comments.
|
|
// Comments that look like a #! hashbang are always left alone.
|
|
type CommentStyle int
|
|
|
|
const (
|
|
// CommentStyleHash means #.
|
|
CommentStyleHash CommentStyle = iota
|
|
// CommentStyleSlash means //.
|
|
CommentStyleSlash
|
|
// CommentStyleLeave means comments are left as they are found.
|
|
CommentStyleLeave
|
|
)
|
|
|
|
// Options is a set of parameters that control the reformatter's behaviour.
|
|
type Options struct {
|
|
// Indent is the number of spaces for each level of indenation.
|
|
Indent int
|
|
// MaxBlankLines is the max allowed number of consecutive blank lines.
|
|
MaxBlankLines int
|
|
StringStyle StringStyle
|
|
CommentStyle CommentStyle
|
|
// PrettyFieldNames causes fields to only be wrapped in '' when needed.
|
|
PrettyFieldNames bool
|
|
// PadArrays causes arrays to be written like [ this ] instead of [this].
|
|
PadArrays bool
|
|
// PadObjects causes arrays to be written like { this } instead of {this}.
|
|
PadObjects bool
|
|
// SortImports causes imports at the top of the file to be sorted in groups
|
|
// by filename.
|
|
SortImports bool
|
|
// UseImplicitPlus removes plus sign where it is not required.
|
|
UseImplicitPlus bool
|
|
|
|
StripEverything bool
|
|
StripComments bool
|
|
StripAllButComments bool
|
|
}
|
|
|
|
// DefaultOptions returns the recommended formatter behaviour.
|
|
func DefaultOptions() Options {
|
|
return Options{
|
|
Indent: 2,
|
|
MaxBlankLines: 2,
|
|
StringStyle: StringStyleSingle,
|
|
CommentStyle: CommentStyleSlash,
|
|
UseImplicitPlus: true,
|
|
PrettyFieldNames: true,
|
|
PadArrays: false,
|
|
PadObjects: true,
|
|
SortImports: true,
|
|
}
|
|
}
|
|
|
|
// If left recursive, return the left hand side, else return nullptr.
|
|
func leftRecursive(expr ast.Node) ast.Node {
|
|
switch node := expr.(type) {
|
|
case *ast.Apply:
|
|
return node.Target
|
|
case *ast.ApplyBrace:
|
|
return node.Left
|
|
case *ast.Binary:
|
|
return node.Left
|
|
case *ast.Index:
|
|
return node.Target
|
|
case *ast.InSuper:
|
|
return node.Index
|
|
case *ast.Slice:
|
|
return node.Target
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// leftRecursiveDeep is the transitive closure of leftRecursive.
|
|
// It only returns nil when called with nil.
|
|
func leftRecursiveDeep(expr ast.Node) ast.Node {
|
|
last := expr
|
|
left := leftRecursive(expr)
|
|
for left != nil {
|
|
last = left
|
|
left = leftRecursive(last)
|
|
}
|
|
return last
|
|
}
|
|
|
|
func openFodder(node ast.Node) *ast.Fodder {
|
|
return leftRecursiveDeep(node).OpenFodder()
|
|
}
|
|
|
|
func removeInitialNewlines(node ast.Node) {
|
|
f := openFodder(node)
|
|
for len(*f) > 0 && (*f)[0].Kind == ast.FodderLineEnd {
|
|
*f = (*f)[1:]
|
|
}
|
|
}
|
|
|
|
func removeExtraTrailingNewlines(finalFodder ast.Fodder) {
|
|
if len(finalFodder) > 0 {
|
|
finalFodder[len(finalFodder)-1].Blanks = 0
|
|
}
|
|
}
|
|
|
|
func visitFile(p pass.ASTPass, node *ast.Node, finalFodder *ast.Fodder) {
|
|
p.File(p, node, finalFodder)
|
|
}
|
|
|
|
// Format returns code that is equivalent to its input but better formatted
|
|
// according to the given options.
|
|
func Format(filename string, input string, options Options) (string, error) {
|
|
node, finalFodder, err := parser.SnippetToRawAST(ast.DiagnosticFileName(filename), "", input)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Passes to enforce style on the AST.
|
|
if options.SortImports {
|
|
SortImports(&node)
|
|
}
|
|
removeInitialNewlines(node)
|
|
if options.MaxBlankLines > 0 {
|
|
visitFile(&EnforceMaxBlankLines{Options: options}, &node, &finalFodder)
|
|
}
|
|
visitFile(&FixNewlines{}, &node, &finalFodder)
|
|
visitFile(&FixTrailingCommas{}, &node, &finalFodder)
|
|
visitFile(&FixParens{}, &node, &finalFodder)
|
|
if options.UseImplicitPlus {
|
|
visitFile(&RemovePlusObject{}, &node, &finalFodder)
|
|
} else {
|
|
visitFile(&AddPlusObject{}, &node, &finalFodder)
|
|
}
|
|
visitFile(&NoRedundantSliceColon{}, &node, &finalFodder)
|
|
if options.StripComments {
|
|
visitFile(&StripComments{}, &node, &finalFodder)
|
|
} else if options.StripAllButComments {
|
|
visitFile(&StripAllButComments{}, &node, &finalFodder)
|
|
} else if options.StripEverything {
|
|
visitFile(&StripEverything{}, &node, &finalFodder)
|
|
}
|
|
if options.PrettyFieldNames {
|
|
visitFile(&PrettyFieldNames{}, &node, &finalFodder)
|
|
}
|
|
if options.StringStyle != StringStyleLeave {
|
|
visitFile(&EnforceStringStyle{Options: options}, &node, &finalFodder)
|
|
}
|
|
if options.CommentStyle != CommentStyleLeave {
|
|
visitFile(&EnforceCommentStyle{Options: options}, &node, &finalFodder)
|
|
}
|
|
if options.Indent > 0 {
|
|
visitor := FixIndentation{Options: options}
|
|
visitor.VisitFile(node, finalFodder)
|
|
}
|
|
removeExtraTrailingNewlines(finalFodder)
|
|
|
|
u := &unparser{options: options}
|
|
u.unparse(node, false)
|
|
u.fillFinal(finalFodder, true, false)
|
|
// Final whitespace is stripped at lexing time. Add a single new line
|
|
// as files ought to end with a new line.
|
|
u.write("\n")
|
|
return u.string(), nil
|
|
}
|