| // 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 |
| } |