blob: 47cefaa6ebf0f0f377d2e99b3df943a880c9d871 [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"
"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
}
switch f := x.Source().(type) {
case *ast.Field:
if hasShorthandValue(f) {
continue
}
fields = append(fields, f)
for _, cg := range f.Comments() {
if !containsDoc(docs, cg) && cg.Doc {
docs = append(docs, cg)
}
}
case *ast.File:
if c := internal.FileComment(f); c != nil {
docs = append(docs, c)
}
}
}
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(v *adt.Vertex) (attrs []*ast.Attribute) {
for _, x := range v.Conjuncts {
attrs = extractFieldAttrs(attrs, x)
}
return attrs
}
func extractFieldAttrs(attrs []*ast.Attribute, c adt.Conjunct) []*ast.Attribute {
if f, ok := c.Source().(*ast.Field); ok {
for _, a := range f.Attrs {
if !containsAttr(attrs, a) {
attrs = append(attrs, a)
}
}
}
return attrs
}
func ExtractDeclAttrs(v *adt.Vertex) (attrs []*ast.Attribute) {
for _, st := range v.Structs {
if src := st.StructLit; src != nil {
attrs = extractDeclAttrs(attrs, src.Src)
}
}
return attrs
}
func extractDeclAttrs(attrs []*ast.Attribute, n ast.Node) []*ast.Attribute {
switch x := n.(type) {
case nil:
case *ast.File:
info := internal.GetPackageInfo(x)
attrs = appendDeclAttrs(attrs, x.Decls[info.Index:])
case *ast.StructLit:
attrs = appendDeclAttrs(attrs, x.Elts)
}
return attrs
}
func appendDeclAttrs(a []*ast.Attribute, decls []ast.Decl) []*ast.Attribute {
for _, d := range decls {
if attr, ok := d.(*ast.Attribute); ok && !containsAttr(a, attr) {
a = append(a, attr)
}
}
return a
}
func containsAttr(a []*ast.Attribute, x *ast.Attribute) bool {
for _, e := range a {
if e.Text == x.Text {
return true
}
}
return false
}