1// Copyright 2019 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
15package format
16
17import (
18	"strconv"
19
20	"cuelang.org/go/cue/ast"
21	"cuelang.org/go/cue/ast/astutil"
22	"cuelang.org/go/internal"
23)
24
25// labelSimplifier rewrites string labels to identifiers if
26// no identifiers will subsequently bind to the exposed label.
27// In other words, string labels are only replaced if this does
28// not change the semantics of the CUE code.
29type labelSimplifier struct {
30	parent *labelSimplifier
31	scope  map[string]bool
32}
33
34func (s *labelSimplifier) processDecls(decls []ast.Decl) {
35	sc := labelSimplifier{parent: s, scope: map[string]bool{}}
36	for _, d := range decls {
37		switch x := d.(type) {
38		case *ast.Field:
39			ast.Walk(x.Label, sc.markStrings, nil)
40		}
41	}
42
43	for _, d := range decls {
44		switch x := d.(type) {
45		case *ast.Field:
46			ast.Walk(x.Value, sc.markReferences, nil)
47		default:
48			ast.Walk(x, sc.markReferences, nil)
49		}
50	}
51
52	for _, d := range decls {
53		switch x := d.(type) {
54		case *ast.Field:
55			x.Label = astutil.Apply(x.Label, sc.replace, nil).(ast.Label)
56		}
57	}
58}
59
60func (s *labelSimplifier) markReferences(n ast.Node) bool {
61	// Record strings at this level.
62	switch x := n.(type) {
63	case *ast.File:
64		s.processDecls(x.Decls)
65		return false
66
67	case *ast.StructLit:
68		s.processDecls(x.Elts)
69		return false
70
71	case *ast.SelectorExpr:
72		ast.Walk(x.X, s.markReferences, nil)
73		return false
74
75	case *ast.Ident:
76		for c := s; c != nil; c = c.parent {
77			if _, ok := c.scope[x.Name]; ok {
78				c.scope[x.Name] = false
79				break
80			}
81		}
82	}
83	return true
84}
85
86func (s *labelSimplifier) markStrings(n ast.Node) bool {
87	switch x := n.(type) {
88	case *ast.BasicLit:
89		str, err := strconv.Unquote(x.Value)
90		if err != nil || !ast.IsValidIdent(str) || internal.IsDefOrHidden(str) {
91			return false
92		}
93		s.scope[str] = true
94
95	case *ast.Ident:
96		s.scope[x.Name] = true
97
98	case *ast.ListLit, *ast.Interpolation:
99		return false
100	}
101	return true
102}
103
104func (s *labelSimplifier) replace(c astutil.Cursor) bool {
105	switch x := c.Node().(type) {
106	case *ast.BasicLit:
107		str, err := strconv.Unquote(x.Value)
108		if err == nil && s.scope[str] && !internal.IsDefOrHidden(str) {
109			c.Replace(ast.NewIdent(str))
110		}
111	}
112	return true
113}
114