blob: b6ccf59bee2bc116b8b22edcfbf9f97741b559a2 [file] [log] [blame]
// Copyright 2020 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 export
import (
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/token"
"cuelang.org/go/internal/core/adt"
)
// ExtractDoc collects documentation strings for a field.
//
// Comments are attached to a field with a field shorthand belong to the
// child node. So in the following the comment is attached to field bar.
//
// // comment
// foo: bar: 2
//
func ExtractDoc(v *adt.Vertex) (docs []*ast.CommentGroup) {
return extractDocs(v, v.Conjuncts)
}
func extractDocs(v *adt.Vertex, a []adt.Conjunct) (docs []*ast.CommentGroup) {
fields := []*ast.Field{}
// Collect docs directly related to this Vertex.
for _, x := range a {
if v, ok := x.Expr().(*adt.Vertex); ok {
docs = append(docs, extractDocs(v, v.Conjuncts)...)
continue
}
f, ok := x.Source().(*ast.Field)
if !ok || hasShorthandValue(f) {
continue
}
fields = append(fields, f)
for _, cg := range f.Comments() {
if !containsDoc(docs, cg) && cg.Doc {
docs = append(docs, cg)
}
}
}
if v == nil {
return docs
}
// Collect docs from parent scopes in collapsed fields.
for p := v.Parent; p != nil; p = p.Parent {
newFields := []*ast.Field{}
for _, x := range p.Conjuncts {
f, ok := x.Source().(*ast.Field)
if !ok || !hasShorthandValue(f) {
continue
}
nested := nestedField(f)
for _, child := range fields {
if nested == child {
newFields = append(newFields, f)
for _, cg := range f.Comments() {
if !containsDoc(docs, cg) && cg.Doc {
docs = append(docs, cg)
}
}
}
}
}
fields = newFields
}
return docs
}
// hasShorthandValue reports whether this field has a struct value that will
// be rendered as a shorthand, for instance:
//
// f: g: 2
//
func hasShorthandValue(f *ast.Field) bool {
if f = nestedField(f); f == nil {
return false
}
// Not a regular field, but shorthand field.
// TODO: Should we return here? For now mimic old implementation.
if _, _, err := ast.LabelName(f.Label); err != nil {
return false
}
return true
}
// nestedField returns the child field of a field shorthand.
func nestedField(f *ast.Field) *ast.Field {
s, _ := f.Value.(*ast.StructLit)
if s == nil ||
len(s.Elts) != 1 ||
s.Lbrace != token.NoPos ||
s.Rbrace != token.NoPos {
return nil
}
f, _ = s.Elts[0].(*ast.Field)
return f
}
func containsDoc(a []*ast.CommentGroup, cg *ast.CommentGroup) bool {
for _, c := range a {
if c == cg {
return true
}
}
for _, c := range a {
if c.Text() == cg.Text() {
return true
}
}
return false
}
func ExtractFieldAttrs(a []adt.Conjunct) (attrs []*ast.Attribute) {
for _, x := range a {
f, ok := x.Source().(*ast.Field)
if !ok {
continue
}
for _, a := range f.Attrs {
if !containsAttr(attrs, a) {
attrs = append(attrs, a)
}
}
}
return attrs
}
func containsAttr(a []*ast.Attribute, x *ast.Attribute) bool {
for _, e := range a {
if e.Text == x.Text {
return true
}
}
return false
}