@@ -100,6 +100,7 @@ func (op opArrHasKeyword) Construct(st *State, n nodes.Node) (nodes.Node, error)
100100 // synthesize the node
101101
102102 // TODO(dennwc): synthesize the node once we care about reverse transform
103+ // see https://github.com/bblfsh/sdk/issues/355
103104 return n , nil
104105}
105106
@@ -126,6 +127,7 @@ func (op opArrToChain) Check(st *State, n nodes.Node) (bool, error) {
126127 var mods nodes.Array
127128
128129 // TODO(dennwc): implement when we will need a reversal
130+ // see https://github.com/bblfsh/sdk/issues/355
129131 if ok , err := op .opType .Check (st , n ); err != nil || ! ok {
130132 return ok , err
131133 }
@@ -782,6 +784,16 @@ var triviaField = map[string]string{
782784 "CompilationUnit" : "Members" ,
783785}
784786
787+ // firstWithType returns an index of the first node type of which matches the filter function.
788+ func firstWithType (arr nodes.Array , fnc func (typ string ) bool ) int {
789+ for i , sub := range arr {
790+ if fnc (uast .TypeOf (sub )) {
791+ return i
792+ }
793+ }
794+ return - 1
795+ }
796+
785797// opMoveTrivias cuts trivia nodes from LeadingTrivia/TrailingTrivia fields
786798// and wraps the node into uast:Group that contains those trivias.
787799type opMoveTrivias struct {
@@ -792,18 +804,38 @@ func (op opMoveTrivias) Kinds() nodes.Kind {
792804 return nodes .KindObject
793805}
794806
807+ // Check implements the logic to move the trivia out of the node into a Group that will
808+ // wrap the trivia and the node itself.
809+ //
810+ // The function is a bit more complex than it should be because there are some edge cases
811+ // where instead of wrapping the node we will add trivia to a specific field of the node.
812+ //
813+ // There are two general cases here that may overlap.
814+ //
815+ // First, some nodes will have a Leading/TrailingTrivia fields. We will remove those fields
816+ // from the node, and will wrap the node itself into a uast:Group with an array consisting
817+ // of (leading trivia + node + trailing trivia).
818+ //
819+ // There are some edge cases here. For example, we don't want to wrap a CompilationUnit nodes
820+ // so we will add trivia nodes to it's Members slice (see triviaField map for all such cases).
821+ //
822+ // But, we can't stop here because the Group will prevent other annotations to match against
823+ // the original node. To fix this, we also try to access few known fields and check if they
824+ // already contain a Group. If it does, we unwrap the node and join its trivia into our
825+ // leading/trailing slice. This is possible because DSL guarantees that transforms always
826+ // happen in DFS order, thus all child nodes were already visited by this transform.
795827func (op opMoveTrivias ) Check (st * State , n nodes.Node ) (bool , error ) {
796828 obj , ok := n .(nodes.Object )
797829 if ! ok {
798830 return false , nil
799831 }
800- cloned := false
832+ modified := false
801833 leading , ok1 := obj ["LeadingTrivia" ].(nodes.Array )
802834 trailing , ok2 := obj ["TrailingTrivia" ].(nodes.Array )
803835 if ok1 || ok2 {
804836 // we saved the leading and trailing trivias, now wipe them from the node
805837 obj = obj .CloneObject ()
806- cloned = true
838+ modified = true
807839 delete (obj , "LeadingTrivia" )
808840 delete (obj , "TrailingTrivia" )
809841 }
@@ -827,32 +859,34 @@ func (op opMoveTrivias) Check(st *State, n nodes.Node) (bool, error) {
827859 }
828860 // we already joined trivias and the node into a single array,
829861 // so we will have to find its index now
830- ind := - 1
831- for i , sub := range arr {
832- if ! strings .HasSuffix (uast .TypeOf (sub ), "Trivia" ) {
833- ind = i
834- break
835- }
836- }
862+ ind := firstWithType (arr , func (typ string ) bool {
863+ return ! strings .HasSuffix (typ , "Trivia" )
864+ })
865+
837866 var node nodes.Node
838867 if ind < 0 {
839868 // cannot determine an index - pretend everything is a leading trivia
840- // TODO(dennwc): find another way if it happens in practice
869+ // TODO(dennwc): this should not happen in practice, since leading/trailing
870+ // trivias are always attached to a node, and this node should
871+ // be somewhere in this slice
872+ // but future annotations may drop this node, so we will need
873+ // to find a different way of splitting leading and trailing
874+ // trivia if it happens in practice
841875 leading = append (leading .CloneList (), arr ... )
842876 } else {
843877 node = arr [ind ]
844878 leading = append (leading .CloneList (), arr [:ind ]... )
845879 trailing = append (arr [ind + 1 :len (arr ):len (arr )], trailing ... )
846880 }
847- if ! cloned {
881+ if ! modified {
848882 obj = obj .CloneObject ()
849- cloned = true
883+ modified = true
850884 }
851885 obj [key ] = node
852886 }
853887
854888 if len (leading ) == 0 && len (trailing ) == 0 {
855- if ! cloned {
889+ if ! modified {
856890 return false , nil // unmodified
857891 }
858892 // removed the trivia fields
@@ -871,32 +905,41 @@ func (op opMoveTrivias) Check(st *State, n nodes.Node) (bool, error) {
871905 arr = append (arr , trailing ... )
872906
873907 obj [field ] = arr
874- } else {
875- // wrap the node into a uast:Group
876- arr := make (nodes.Array , 0 , len (leading )+ 1 + len (trailing ))
877- arr = append (arr , leading ... )
878- arr = append (arr , obj )
879- arr = append (arr , trailing ... )
908+ return op .sub .Check (st , obj )
909+ }
880910
881- // TODO(dennwc): it will be nice if we could extract FullSpan position into the Group
882- group , err := uast .ToNode (uast.Group {})
883- if err != nil {
884- return false , err
885- }
886- obj = group .(nodes.Object )
887- obj ["Nodes" ] = arr
911+ // wrap the node into a uast:Group
912+ arr := make (nodes.Array , 0 , len (leading )+ 1 + len (trailing ))
913+ arr = append (arr , leading ... )
914+ arr = append (arr , obj )
915+ arr = append (arr , trailing ... )
916+
917+ // TODO(dennwc): it will be nice if we could extract FullSpan position into the Group
918+ group , err := uast .ToNode (uast.Group {})
919+ if err != nil {
920+ return false , err
888921 }
889922
923+ // note that we overwrite a variable - it was the current node
924+ // and now it is a Group wrapping the current node
925+ obj = group .(nodes.Object )
926+ obj ["Nodes" ] = arr
890927 return op .sub .Check (st , obj )
891928}
892929
893930func (op opMoveTrivias ) Construct (st * State , n nodes.Node ) (nodes.Node , error ) {
894931 // TODO(dennwc): implement when we will need a reversal
932+ // see https://github.com/bblfsh/sdk/issues/355
895933 return op .sub .Construct (st , n )
896934}
897935
898- // opMergeGroups find the uast:Group nodes and merges them into a child
936+ // opMergeGroups finds the uast:Group nodes and merges them into a child
899937// uast:FunctionGroup, if it exists.
938+ //
939+ // This transform is necessary because opMoveTrivias will wrap all nodes that contain trivia
940+ // into a Group node, and the same will happen with MethodDeclaration nodes. But according
941+ // to a UAST schema defined in SDK, the comments (trivia) should be directly inside the
942+ // FunctionGroup node that wraps functions in Semantic mode.
900943type opMergeGroups struct {
901944 sub Op
902945}
@@ -905,6 +948,8 @@ func (op opMergeGroups) Kinds() nodes.Kind {
905948 return nodes .KindObject
906949}
907950
951+ // Check tests if the current node is uast:Group and if it contains a uast:FunctionGroup
952+ // node, it will remove the current node and merge other children into the FunctionGroup.
908953func (op opMergeGroups ) Check (st * State , n nodes.Node ) (bool , error ) {
909954 group , ok := n .(nodes.Object )
910955 if ! ok || uast .TypeOf (group ) != typeGroup {
@@ -914,13 +959,9 @@ func (op opMergeGroups) Check(st *State, n nodes.Node) (bool, error) {
914959 if ! ok {
915960 return false , errors .New ("expected an array in Group.Nodes" )
916961 }
917- ind := - 1
918- for i , sub := range arr {
919- if uast .TypeOf (sub ) == typeFuncGroup {
920- ind = i
921- break
922- }
923- }
962+ ind := firstWithType (arr , func (typ string ) bool {
963+ return typ == typeFuncGroup
964+ })
924965 if ind < 0 {
925966 return false , nil
926967 }
@@ -945,5 +986,6 @@ func (op opMergeGroups) Check(st *State, n nodes.Node) (bool, error) {
945986
946987func (op opMergeGroups ) Construct (st * State , n nodes.Node ) (nodes.Node , error ) {
947988 // TODO(dennwc): implement when we will need a reversal
989+ // see https://github.com/bblfsh/sdk/issues/355
948990 return op .sub .Construct (st , n )
949991}
0 commit comments