Marcel van Lohuizen | b001da5 | 2018-12-10 15:34:30 +0100 | [diff] [blame] | 1 | // Copyright 2018 The CUE Authors |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | // Package format implements standard formatting of CUE configurations. |
| 16 | package format // import "cuelang.org/go/cue/format" |
| 17 | |
| 18 | import ( |
| 19 | "bytes" |
| 20 | "fmt" |
| 21 | "io" |
| 22 | "strings" |
| 23 | "text/tabwriter" |
| 24 | |
| 25 | "cuelang.org/go/cue/ast" |
| 26 | "cuelang.org/go/cue/parser" |
| 27 | "cuelang.org/go/cue/token" |
| 28 | ) |
| 29 | |
| 30 | // An Option sets behavior of the formatter. |
| 31 | type Option func(c *config) |
| 32 | |
| 33 | // FileSet sets the file set that was used for creating a node. The formatter |
| 34 | // generally formats fine without it. |
| 35 | func FileSet(fset *token.FileSet) Option { |
| 36 | return func(c *config) { c.fset = fset } |
| 37 | } |
| 38 | |
| 39 | // Simplify allows the formatter to simplify output, such as removing |
| 40 | // unnecessary quotes. |
| 41 | func Simplify() Option { |
| 42 | return func(c *config) { c.simplify = true } |
| 43 | } |
| 44 | |
| 45 | // TODO: other options: |
| 46 | // |
| 47 | // const ( |
| 48 | // RawFormat Mode = 1 << iota // do not use a tabwriter; if set, UseSpaces is ignored |
| 49 | // TabIndent // use tabs for indentation independent of UseSpaces |
| 50 | // UseSpaces // use spaces instead of tabs for alignment |
| 51 | // SourcePos // emit //line comments to preserve original source positions |
| 52 | // ) |
| 53 | |
| 54 | // Node formats node in canonical cue fmt style and writes the result to dst. |
| 55 | // |
| 56 | // The node type must be *ast.File, []syntax.Decl, syntax.Expr, syntax.Decl, or |
| 57 | // syntax.Spec. Node does not modify node. Imports are not sorted for nodes |
| 58 | // representing partial source files (for instance, if the node is not an |
| 59 | // *ast.File). |
| 60 | // |
| 61 | // The function may return early (before the entire result is written) and |
| 62 | // return a formatting error, for instance due to an incorrect AST. |
| 63 | // |
| 64 | func Node(dst io.Writer, node ast.Node, opt ...Option) error { |
| 65 | cfg := newConfig(opt) |
| 66 | return cfg.fprint(dst, node) |
| 67 | } |
| 68 | |
| 69 | // Source formats src in canonical cue fmt style and returns the result or an |
| 70 | // (I/O or syntax) error. src is expected to be a syntactically correct CUE |
| 71 | // source file, or a list of CUE declarations or statements. |
| 72 | // |
| 73 | // If src is a partial source file, the leading and trailing space of src is |
| 74 | // applied to the result (such that it has the same leading and trailing space |
| 75 | // as src), and the result is indented by the same amount as the first line of |
| 76 | // src containing code. Imports are not sorted for partial source files. |
| 77 | // |
| 78 | // Caution: Tools relying on consistent formatting based on the installed |
| 79 | // version of cue (for instance, such as for presubmit checks) should execute |
| 80 | // that cue binary instead of calling Source. |
| 81 | // |
| 82 | func Source(b []byte, opt ...Option) ([]byte, error) { |
| 83 | cfg := newConfig(opt) |
| 84 | if cfg.fset == nil { |
| 85 | cfg.fset = token.NewFileSet() |
| 86 | } |
| 87 | |
Marcel van Lohuizen | 2bf066f | 2018-12-16 11:43:49 +0100 | [diff] [blame^] | 88 | f, err := parser.ParseFile(cfg.fset, "", b, parser.ParseComments) |
Marcel van Lohuizen | b001da5 | 2018-12-10 15:34:30 +0100 | [diff] [blame] | 89 | if err != nil { |
| 90 | return nil, fmt.Errorf("parse: %s", err) |
| 91 | } |
| 92 | |
| 93 | // print AST |
| 94 | var buf bytes.Buffer |
| 95 | if err := cfg.fprint(&buf, f); err != nil { |
| 96 | return nil, fmt.Errorf("print: %s", err) |
| 97 | } |
| 98 | return buf.Bytes(), nil |
| 99 | } |
| 100 | |
| 101 | type config struct { |
| 102 | fset *token.FileSet |
| 103 | |
| 104 | UseSpaces bool |
| 105 | TabIndent bool |
| 106 | Tabwidth int // default: 8 |
| 107 | Indent int // default: 0 (all code is indented at least by this much) |
| 108 | |
| 109 | simplify bool |
| 110 | } |
| 111 | |
| 112 | func newConfig(opt []Option) *config { |
| 113 | cfg := &config{Tabwidth: 8, TabIndent: true, UseSpaces: true} |
| 114 | for _, o := range opt { |
| 115 | o(cfg) |
| 116 | } |
| 117 | return cfg |
| 118 | } |
| 119 | |
| 120 | // Config defines the output of Fprint. |
| 121 | func (cfg *config) fprint(output io.Writer, node interface{}) (err error) { |
| 122 | // print node |
| 123 | var p printer |
| 124 | p.init(cfg) |
| 125 | if err = printNode(node, &p); err != nil { |
| 126 | return err |
| 127 | } |
| 128 | |
| 129 | minwidth := cfg.Tabwidth |
| 130 | |
| 131 | padchar := byte('\t') |
| 132 | if cfg.UseSpaces { |
| 133 | padchar = ' ' |
| 134 | } |
| 135 | |
| 136 | twmode := tabwriter.DiscardEmptyColumns | tabwriter.StripEscape |
| 137 | if cfg.TabIndent { |
| 138 | minwidth = 0 |
| 139 | twmode |= tabwriter.TabIndent |
| 140 | } |
| 141 | |
| 142 | tw := tabwriter.NewWriter(output, minwidth, cfg.Tabwidth, 1, padchar, twmode) |
| 143 | |
| 144 | // write printer result via tabwriter/trimmer to output |
| 145 | if _, err = tw.Write(p.output); err != nil { |
| 146 | return |
| 147 | } |
| 148 | |
| 149 | err = tw.Flush() |
| 150 | return |
| 151 | } |
| 152 | |
| 153 | // A formatter walks a syntax.Node, interspersed with comments and spacing |
| 154 | // directives, in the order that they would occur in printed form. |
| 155 | type formatter struct { |
| 156 | *printer |
| 157 | |
| 158 | stack []frame |
| 159 | current frame |
| 160 | nestExpr int |
| 161 | |
| 162 | labelBuf []ast.Label |
| 163 | } |
| 164 | |
| 165 | func newFormatter(p *printer) *formatter { |
| 166 | f := &formatter{ |
| 167 | printer: p, |
| 168 | current: frame{ |
| 169 | settings: settings{ |
| 170 | nodeSep: newline, |
| 171 | parentSep: newline, |
| 172 | }, |
| 173 | }, |
| 174 | } |
| 175 | return f |
| 176 | } |
| 177 | |
| 178 | type whiteSpace int |
| 179 | |
| 180 | const ( |
| 181 | ignore whiteSpace = 0 |
| 182 | |
| 183 | // write a space, or disallow it |
| 184 | blank whiteSpace = 1 << iota |
| 185 | vtab // column marker |
| 186 | noblank |
| 187 | |
| 188 | nooverride |
| 189 | |
| 190 | comma // print a comma, unless trailcomma overrides it |
| 191 | trailcomma // print a trailing comma unless closed on same line |
| 192 | declcomma // write a comma when not at the end of line |
| 193 | |
| 194 | newline // write a line in a table |
| 195 | formfeed // next line is not part of the table |
| 196 | newsection // add two newlines |
| 197 | |
| 198 | indent // request indent an extra level after the next newline |
| 199 | unindent // unindent a level after the next newline |
| 200 | indented // element was indented. |
| 201 | ) |
| 202 | |
| 203 | type frame struct { |
| 204 | cg []*ast.CommentGroup |
| 205 | pos int8 |
| 206 | |
| 207 | settings |
| 208 | commentNewline bool |
| 209 | } |
| 210 | |
| 211 | type settings struct { |
| 212 | // separator is blank if the current node spans a single line and newline |
| 213 | // otherwise. |
| 214 | nodeSep whiteSpace |
| 215 | parentSep whiteSpace |
| 216 | override whiteSpace |
| 217 | } |
| 218 | |
| 219 | func (f *formatter) print(a ...interface{}) { |
| 220 | for _, x := range a { |
| 221 | f.Print(x) |
| 222 | switch x.(type) { |
| 223 | case string, token.Token: // , *syntax.BasicLit, *syntax.Ident: |
| 224 | f.current.pos++ |
| 225 | } |
| 226 | } |
| 227 | f.visitComments(f.current.pos) |
| 228 | } |
| 229 | |
| 230 | func (f *formatter) formfeed() whiteSpace { |
| 231 | if f.current.nodeSep == blank { |
| 232 | return blank |
| 233 | } |
| 234 | return formfeed |
| 235 | } |
| 236 | |
| 237 | func (f *formatter) wsOverride(def whiteSpace) whiteSpace { |
| 238 | if f.current.override == ignore { |
| 239 | return def |
| 240 | } |
| 241 | return f.current.override |
| 242 | } |
| 243 | |
| 244 | func (f *formatter) onOneLine(node ast.Node) bool { |
| 245 | a := node.Pos() |
| 246 | b := node.End() |
| 247 | if f.fset != nil && a.IsValid() && b.IsValid() { |
| 248 | return f.lineFor(a) == f.lineFor(b) |
| 249 | } |
| 250 | // TODO: walk and look at relative positions to determine the same? |
| 251 | return false |
| 252 | } |
| 253 | |
| 254 | func (f *formatter) before(node ast.Node) bool { |
| 255 | f.stack = append(f.stack, f.current) |
| 256 | f.current = frame{settings: f.current.settings} |
| 257 | f.current.parentSep = f.current.nodeSep |
| 258 | |
| 259 | if node != nil { |
| 260 | if f.current.nodeSep != blank && f.onOneLine(node) { |
| 261 | f.current.nodeSep = blank |
| 262 | } |
| 263 | f.current.cg = node.Comments() |
| 264 | f.visitComments(f.current.pos) |
| 265 | return true |
| 266 | } |
| 267 | return false |
| 268 | } |
| 269 | |
| 270 | func (f *formatter) after(node ast.Node) { |
| 271 | f.visitComments(127) |
| 272 | p := len(f.stack) - 1 |
| 273 | f.current = f.stack[p] |
| 274 | f.stack = f.stack[:p] |
| 275 | f.current.pos++ |
| 276 | f.visitComments(f.current.pos) |
| 277 | } |
| 278 | |
| 279 | func (f *formatter) visitComments(until int8) { |
| 280 | c := &f.current |
| 281 | |
| 282 | for ; len(c.cg) > 0 && c.cg[0].Position <= until; c.cg = c.cg[1:] { |
| 283 | f.Print(c.cg[0]) |
| 284 | |
| 285 | printBlank := false |
| 286 | if c.cg[0].Doc { |
| 287 | f.Print(newline) |
| 288 | printBlank = true |
| 289 | } |
| 290 | for _, c := range c.cg[0].List { |
| 291 | if !printBlank { |
| 292 | f.Print(vtab) |
| 293 | } |
| 294 | f.Print(c.Slash) |
| 295 | f.Print(c) |
| 296 | if strings.HasPrefix(c.Text, "//") { |
| 297 | f.Print(newline) |
| 298 | } |
| 299 | } |
| 300 | } |
| 301 | } |