mirror of
https://github.com/google/go-jsonnet.git
synced 2025-08-08 07:17:12 +02:00
We used to treat dummy paths like <stdin>, <std>, <extvar> as real import locations, which causes obvious problem for importers. After this change we consistently pass "" (an empty string) as location for non-imported paths. We exposed new functions to properly handle all paths. The first function family, EvaluateFile* which allow evaluating a Jsonnet program at a specified importable path. It should be used in most cases. The second function family, EvaluateAnonymousSnippet* allows evaluating a snippet without an importable path. It should be used for situations like the code passed as a commandline argument, stdin, etc. The old function family, EvaluateSnippet* is now deprecated, but works the same as before, i.e. the passed filenames are treated as imported paths. Changes are required to custom importers to make sure they satisfy the refined contract. Fixes #329.
186 lines
5.3 KiB
Go
186 lines
5.3 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
|
|
|
|
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
|
|
|
|
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,
|
|
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 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)
|
|
visitFile(&FixPlusObject{}, &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)
|
|
}
|
|
|
|
u := &unparser{options: options}
|
|
u.unparse(node, false)
|
|
u.fill(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
|
|
}
|