github.com/vugu/vugu@v0.3.6-0.20240430171613-3f6f402e014b/gen/parser-util.go (about)

     1  package gen
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/parser"
     7  	"go/printer"
     8  	"go/token"
     9  	"io"
    10  	"sort"
    11  	"strings"
    12  
    13  	// "github.com/vugu/vugu/internal/htmlx"
    14  
    15  	// "golang.org/x/net/html"
    16  	"github.com/vugu/html"
    17  
    18  	"github.com/vugu/vugu"
    19  )
    20  
    21  func attrFromHtml(attr html.Attribute) vugu.VGAttribute {
    22  	return vugu.VGAttribute{
    23  		Namespace: attr.Namespace,
    24  		Key:       attr.OrigKey,
    25  		Val:       attr.Val,
    26  	}
    27  }
    28  
    29  // func attrFromHtmlx(attr htmlx.Attribute) vugu.VGAttribute {
    30  // 	return vugu.VGAttribute{
    31  // 		Namespace: attr.Namespace,
    32  // 		Key:       attr.Key,
    33  // 		Val:       attr.Val,
    34  // 	}
    35  // }
    36  
    37  // stuff that is common to both parsers can get moved into here
    38  
    39  func staticVGAttr(inAttr []html.Attribute) (ret []vugu.VGAttribute) {
    40  
    41  	for _, a := range inAttr {
    42  		switch {
    43  		// case a.Key == "vg-if":
    44  		// case a.Key == "vg-for":
    45  		// case a.Key == "vg-key":
    46  		// case a.Key == "vg-html":
    47  		case strings.HasPrefix(a.Key, "vg-"):
    48  		case strings.HasPrefix(a.Key, "."):
    49  		case strings.HasPrefix(a.Key, ":"):
    50  		case strings.HasPrefix(a.Key, "@"):
    51  		default:
    52  			ret = append(ret, attrFromHtml(a))
    53  		}
    54  	}
    55  
    56  	return ret
    57  }
    58  
    59  func vgSlotName(n *html.Node) string {
    60  	for _, a := range n.Attr {
    61  		if a.Key == "name" {
    62  			return a.Val
    63  		}
    64  	}
    65  	return ""
    66  }
    67  
    68  func vgVarExpr(n *html.Node) string {
    69  	for _, a := range n.Attr {
    70  		if a.Key == "vg-var" {
    71  			return a.Val
    72  		}
    73  	}
    74  	return ""
    75  }
    76  
    77  func vgIfExpr(n *html.Node) string {
    78  	for _, a := range n.Attr {
    79  		if a.Key == "vg-if" {
    80  			return a.Val
    81  		}
    82  	}
    83  	return ""
    84  }
    85  
    86  func vgKeyExpr(n *html.Node) string {
    87  	for _, a := range n.Attr {
    88  		if a.Key == "vg-key" {
    89  			return a.Val
    90  		}
    91  	}
    92  	return ""
    93  }
    94  
    95  func vgCompExpr(n *html.Node) string {
    96  	for _, a := range n.Attr {
    97  		if a.Key == "expr" {
    98  			return a.Val
    99  		}
   100  	}
   101  	return ""
   102  }
   103  
   104  // func vgIfExprx(n *htmlx.Node) string {
   105  // 	for _, a := range n.Attr {
   106  // 		if a.Key == "vg-if" {
   107  // 			return a.Val
   108  // 		}
   109  // 	}
   110  // 	return ""
   111  // }
   112  
   113  type vgForAttr struct {
   114  	expr     string
   115  	noshadow bool
   116  }
   117  
   118  func vgForExpr(n *html.Node) (vgForAttr, error) {
   119  	for _, a := range n.Attr {
   120  		if strings.HasPrefix(a.Key, "vg-for") {
   121  			v := vgForAttr{expr: strings.TrimSpace(a.Val)}
   122  			opts := strings.Split(a.Key, ".")
   123  			if len(opts) > 1 {
   124  				for _, opt := range opts[1:] {
   125  					switch opt {
   126  					case "noshadow":
   127  						v.noshadow = true
   128  					default:
   129  						return vgForAttr{}, fmt.Errorf("option %q unknown", opt)
   130  					}
   131  				}
   132  			}
   133  			return v, nil
   134  		}
   135  	}
   136  	return vgForAttr{}, nil
   137  }
   138  
   139  func vgHTMLExpr(n *html.Node) string {
   140  	for _, a := range n.Attr {
   141  		// vg-html and vg-content are the same thing,
   142  		// the name vg-content was introduced to call out
   143  		// the difference between Vue's v-html attribute
   144  		// which does not perform escaping.
   145  		if a.Key == "vg-html" {
   146  			return a.Val
   147  		}
   148  		if a.Key == "vg-content" {
   149  			return a.Val
   150  		}
   151  	}
   152  	return ""
   153  }
   154  
   155  // extract ":attr" stuff from a node
   156  func dynamicVGAttrExpr(n *html.Node) (ret map[string]string, retKeys []string) {
   157  	var da []html.Attribute
   158  	// get dynamic attrs first
   159  	for _, a := range n.Attr {
   160  		// ":" and "vg-attr" are the AttributeLister case
   161  		if strings.HasPrefix(a.OrigKey, ":") || a.OrigKey == "vg-attr" {
   162  			da = append(da, a)
   163  		}
   164  	}
   165  	if len(da) == 0 { // don't allocate map if we don't have to
   166  		return
   167  	}
   168  	// make map as small as possible
   169  	ret = make(map[string]string, len(da))
   170  	retKeys = make([]string, len(da))
   171  	for i, a := range da {
   172  		k := strings.TrimPrefix(a.OrigKey, ":")
   173  		retKeys[i] = k
   174  		ret[k] = a.Val
   175  	}
   176  	sort.Strings(retKeys)
   177  	return
   178  }
   179  
   180  // extract ".prop" stuff from a node
   181  func propVGAttrExpr(n *html.Node) (ret map[string]string, retKeys []string) {
   182  	var da []html.Attribute
   183  	// get prop attrs first
   184  	for _, a := range n.Attr {
   185  		if strings.HasPrefix(a.OrigKey, ".") {
   186  			da = append(da, a)
   187  		}
   188  	}
   189  	if len(da) == 0 { // don't allocate map if we don't have to
   190  		return
   191  	}
   192  	// make map as small as possible
   193  	ret = make(map[string]string, len(da))
   194  	retKeys = make([]string, len(da))
   195  	for i, a := range da {
   196  		k := strings.TrimPrefix(a.OrigKey, ".")
   197  		retKeys[i] = k
   198  		ret[k] = a.Val
   199  	}
   200  	sort.Strings(retKeys)
   201  	return
   202  }
   203  
   204  // returns vg-js-create and vg-js-populate
   205  func jsCallbackVGAttrExpr(n *html.Node) (ret map[string]string) {
   206  	for _, attr := range n.Attr {
   207  		if strings.HasPrefix(attr.OrigKey, "vg-js-") {
   208  			if ret == nil {
   209  				ret = make(map[string]string, 2)
   210  			}
   211  			ret[attr.OrigKey] = attr.Val
   212  		}
   213  	}
   214  	return ret
   215  }
   216  
   217  func vgDOMEventExprs(n *html.Node) (ret map[string]string, retKeys []string) {
   218  	return vgEventExprs(n)
   219  }
   220  
   221  // extract "@event" stuff from a node
   222  func vgEventExprs(n *html.Node) (ret map[string]string, retKeys []string) {
   223  	var da []html.Attribute
   224  	// get attrs first
   225  	for _, a := range n.Attr {
   226  		if strings.HasPrefix(a.OrigKey, "@") {
   227  			da = append(da, a)
   228  		}
   229  	}
   230  	if len(da) == 0 { // don't allocate map if we don't have to
   231  		return
   232  	}
   233  	// make map as small as possible
   234  	ret = make(map[string]string, len(da))
   235  	for _, a := range da {
   236  		k := strings.TrimPrefix(a.OrigKey, "@")
   237  		retKeys = append(retKeys, k)
   238  		ret[k] = a.Val
   239  	}
   240  	return
   241  }
   242  
   243  // var vgDOMParseExprRE = regexp.MustCompile(`^([a-zA-Z0-9_.]+)\((.*)\)$`)
   244  
   245  // func vgDOMParseExpr(expr string) (receiver string, methodName string, argList string) {
   246  // 	parts := vgDOMParseExprRE.FindStringSubmatch(expr)
   247  // 	if len(parts) != 3 {
   248  // 		return
   249  // 	}
   250  // 	argList = parts[2]
   251  // 	f := parts[1]
   252  // 	fparts := strings.Split(f, ".")
   253  
   254  // 	receiver, methodName = strings.Join(fparts[:len(fparts)-1], "."), fparts[len(fparts)-1]
   255  
   256  // 	// if len(fparts) == 1 { // just "methodName"
   257  // 	// 	methodName = f
   258  // 	// } else if len(fparts) > 2 { // "a.b.MethodName"
   259  // 	// 	receiver, methodName = strings.Join(fparts[:len(fparts)-1], "."), fparts[len(fparts)-1]
   260  // 	// } else { // "a.MethodName"
   261  // 	// 	receiver, methodName = fparts[0], fparts[1]
   262  // 	// }
   263  // 	return
   264  // }
   265  
   266  // ^([a-zA-Z0-9_.]+)\((.*)\)$
   267  
   268  // dedupImports reads Go source and removes duplicate import statements.
   269  func dedupImports(r io.Reader, w io.Writer, fname string) error {
   270  
   271  	fset := token.NewFileSet() // positions are relative to fset
   272  	f, err := parser.ParseFile(fset, fname, r, parser.AllErrors|parser.ParseComments)
   273  	if err != nil {
   274  		return err
   275  	}
   276  
   277  	// ast.Print(fset, f)
   278  	// ast.Print(fset, f)
   279  
   280  	// ast.Print(fset, f.Decls)
   281  	// f.Decls = f.Decls[1:]
   282  	dedupAstFileImports(f)
   283  	ast.SortImports(fset, f)
   284  
   285  	err = printer.Fprint(w, fset, f)
   286  	if err != nil {
   287  		return err
   288  	}
   289  
   290  	return nil
   291  }
   292  
   293  func dedupAstFileImports(f *ast.File) {
   294  
   295  	imap := make(map[string]bool, len(f.Imports)+10)
   296  
   297  	outdecls := make([]ast.Decl, 0, len(f.Decls))
   298  	for _, decl := range f.Decls {
   299  
   300  		// check for import declaration
   301  		genDecl, _ := decl.(*ast.GenDecl)
   302  		// not an import declaration, just copy and continue
   303  		if genDecl == nil || genDecl.Tok != token.IMPORT {
   304  			outdecls = append(outdecls, decl)
   305  			continue
   306  		}
   307  
   308  		// for imports, we loop over each ImportSpec (each package, regardless of which form of import statement)
   309  		outspecs := make([]ast.Spec, 0, len(genDecl.Specs))
   310  		for _, spec := range genDecl.Specs {
   311  			ispec := spec.(*ast.ImportSpec)
   312  			// always use path
   313  			key := ispec.Path.Value
   314  			// if name is present, prepend
   315  			if ispec.Name != nil {
   316  				key = ispec.Name.Name + " " + key
   317  			}
   318  
   319  			// if we've seen this import before, then just move to the next
   320  			if imap[key] {
   321  				continue
   322  			}
   323  			imap[key] = true // mark this import as having been seen
   324  
   325  			// keep the import
   326  			outspecs = append(outspecs, ispec)
   327  		}
   328  
   329  		// use outspecs for this import decl, unless it's empty in which case we remove/skip the whole import decl
   330  		if len(outspecs) == 0 {
   331  			continue
   332  		}
   333  		genDecl.Specs = outspecs
   334  		outdecls = append(outdecls, genDecl)
   335  
   336  	}
   337  	f.Decls = outdecls
   338  
   339  }