| // Copyright 2019 CUE Authors |
| // |
| // 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 format |
| |
| import ( |
| "strconv" |
| |
| "cuelang.org/go/cue/ast" |
| "cuelang.org/go/cue/ast/astutil" |
| "cuelang.org/go/internal" |
| ) |
| |
| // labelSimplifier rewrites string labels to identifiers if |
| // no identifiers will subsequently bind to the exposed label. |
| // In other words, string labels are only replaced if this does |
| // not change the semantics of the CUE code. |
| type labelSimplifier struct { |
| parent *labelSimplifier |
| scope map[string]bool |
| } |
| |
| func (s *labelSimplifier) processDecls(decls []ast.Decl) { |
| sc := labelSimplifier{parent: s, scope: map[string]bool{}} |
| for _, d := range decls { |
| switch x := d.(type) { |
| case *ast.Field: |
| ast.Walk(x.Label, sc.markStrings, nil) |
| } |
| } |
| |
| for _, d := range decls { |
| switch x := d.(type) { |
| case *ast.Field: |
| ast.Walk(x.Value, sc.markReferences, nil) |
| default: |
| ast.Walk(x, sc.markReferences, nil) |
| } |
| } |
| |
| for _, d := range decls { |
| switch x := d.(type) { |
| case *ast.Field: |
| x.Label = astutil.Apply(x.Label, sc.replace, nil).(ast.Label) |
| } |
| } |
| } |
| |
| func (s *labelSimplifier) markReferences(n ast.Node) bool { |
| // Record strings at this level. |
| switch x := n.(type) { |
| case *ast.File: |
| s.processDecls(x.Decls) |
| return false |
| |
| case *ast.StructLit: |
| s.processDecls(x.Elts) |
| return false |
| |
| case *ast.SelectorExpr: |
| ast.Walk(x.X, s.markReferences, nil) |
| return false |
| |
| case *ast.Ident: |
| for c := s; c != nil; c = c.parent { |
| if _, ok := c.scope[x.Name]; ok { |
| c.scope[x.Name] = false |
| break |
| } |
| } |
| } |
| return true |
| } |
| |
| func (s *labelSimplifier) markStrings(n ast.Node) bool { |
| switch x := n.(type) { |
| case *ast.BasicLit: |
| str, err := strconv.Unquote(x.Value) |
| if err != nil || !ast.IsValidIdent(str) || internal.IsDefOrHidden(str) { |
| return false |
| } |
| s.scope[str] = true |
| |
| case *ast.Ident: |
| s.scope[x.Name] = true |
| |
| case *ast.ListLit, *ast.Interpolation: |
| return false |
| } |
| return true |
| } |
| |
| func (s *labelSimplifier) replace(c astutil.Cursor) bool { |
| switch x := c.Node().(type) { |
| case *ast.BasicLit: |
| str, err := strconv.Unquote(x.Value) |
| if err == nil && s.scope[str] && !internal.IsDefOrHidden(str) { |
| c.Replace(ast.NewIdent(str)) |
| } |
| } |
| return true |
| } |