github.com/gopherjs/gopherjs@v1.19.0-beta1.0.20240506212314-27071a8796e4/compiler/utils.go (about) 1 package compiler 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "errors" 7 "fmt" 8 "go/ast" 9 "go/constant" 10 "go/token" 11 "go/types" 12 "net/url" 13 "regexp" 14 "runtime/debug" 15 "sort" 16 "strconv" 17 "strings" 18 "text/template" 19 "unicode" 20 21 "github.com/gopherjs/gopherjs/compiler/analysis" 22 "github.com/gopherjs/gopherjs/compiler/internal/typeparams" 23 "github.com/gopherjs/gopherjs/compiler/typesutil" 24 ) 25 26 // root returns the topmost function context corresponding to the package scope. 27 func (fc *funcContext) root() *funcContext { 28 if fc.parent == nil { 29 return fc 30 } 31 return fc.parent.root() 32 } 33 34 func (fc *funcContext) Write(b []byte) (int, error) { 35 fc.writePos() 36 fc.output = append(fc.output, b...) 37 return len(b), nil 38 } 39 40 func (fc *funcContext) Printf(format string, values ...interface{}) { 41 fc.Write([]byte(fc.Indentation(0))) 42 fmt.Fprintf(fc, format, values...) 43 fc.Write([]byte{'\n'}) 44 fc.Write(fc.delayedOutput) 45 fc.delayedOutput = nil 46 } 47 48 func (fc *funcContext) PrintCond(cond bool, onTrue, onFalse string) { 49 if !cond { 50 fc.Printf("/* %s */ %s", strings.Replace(onTrue, "*/", "<star>/", -1), onFalse) 51 return 52 } 53 fc.Printf("%s", onTrue) 54 } 55 56 func (fc *funcContext) SetPos(pos token.Pos) { 57 fc.posAvailable = true 58 fc.pos = pos 59 } 60 61 func (fc *funcContext) writePos() { 62 if fc.posAvailable { 63 fc.posAvailable = false 64 fc.Write([]byte{'\b'}) 65 binary.Write(fc, binary.BigEndian, uint32(fc.pos)) 66 } 67 } 68 69 // Indented increases generated code indentation level by 1 for the code emitted 70 // from the callback f. 71 func (fc *funcContext) Indented(f func()) { 72 fc.pkgCtx.indentation++ 73 f() 74 fc.pkgCtx.indentation-- 75 } 76 77 // Indentation returns a sequence of "\t" characters appropriate to the current 78 // generated code indentation level. The `extra` parameter provides relative 79 // indentation adjustment. 80 func (fc *funcContext) Indentation(extra int) string { 81 return strings.Repeat("\t", fc.pkgCtx.indentation+extra) 82 } 83 84 func (fc *funcContext) CatchOutput(indent int, f func()) []byte { 85 origoutput := fc.output 86 fc.output = nil 87 fc.pkgCtx.indentation += indent 88 f() 89 fc.writePos() 90 caught := fc.output 91 fc.output = origoutput 92 fc.pkgCtx.indentation -= indent 93 return caught 94 } 95 96 func (fc *funcContext) Delayed(f func()) { 97 fc.delayedOutput = fc.CatchOutput(0, f) 98 } 99 100 // expandTupleArgs converts a function call which argument is a tuple returned 101 // by another function into a set of individual call arguments corresponding to 102 // tuple elements. 103 // 104 // For example, for functions defined as: 105 // 106 // func a() (int, string) {return 42, "foo"} 107 // func b(a1 int, a2 string) {} 108 // 109 // ...the following statement: 110 // 111 // b(a()) 112 // 113 // ...will be transformed into: 114 // 115 // _tuple := a() 116 // b(_tuple[0], _tuple[1]) 117 func (fc *funcContext) expandTupleArgs(argExprs []ast.Expr) []ast.Expr { 118 if len(argExprs) != 1 { 119 return argExprs 120 } 121 122 tuple, isTuple := fc.typeOf(argExprs[0]).(*types.Tuple) 123 if !isTuple { 124 return argExprs 125 } 126 127 tupleVar := fc.newLocalVariable("_tuple") 128 fc.Printf("%s = %s;", tupleVar, fc.translateExpr(argExprs[0])) 129 argExprs = make([]ast.Expr, tuple.Len()) 130 for i := range argExprs { 131 argExprs[i] = fc.newIdent(fc.formatExpr("%s[%d]", tupleVar, i).String(), tuple.At(i).Type()) 132 } 133 return argExprs 134 } 135 136 func (fc *funcContext) translateArgs(sig *types.Signature, argExprs []ast.Expr, ellipsis bool) []string { 137 argExprs = fc.expandTupleArgs(argExprs) 138 139 sigTypes := typesutil.Signature{Sig: sig} 140 141 if sig.Variadic() && len(argExprs) == 0 { 142 return []string{fmt.Sprintf("%s.nil", fc.typeName(sigTypes.VariadicType()))} 143 } 144 145 preserveOrder := false 146 for i := 1; i < len(argExprs); i++ { 147 preserveOrder = preserveOrder || fc.Blocking[argExprs[i]] 148 } 149 150 args := make([]string, len(argExprs)) 151 for i, argExpr := range argExprs { 152 arg := fc.translateImplicitConversionWithCloning(argExpr, sigTypes.Param(i, ellipsis)).String() 153 154 if preserveOrder && fc.pkgCtx.Types[argExpr].Value == nil { 155 argVar := fc.newLocalVariable("_arg") 156 fc.Printf("%s = %s;", argVar, arg) 157 arg = argVar 158 } 159 160 args[i] = arg 161 } 162 163 // If variadic arguments were passed in as individual elements, regroup them 164 // into a slice and pass it as a single argument. 165 if sig.Variadic() && !ellipsis { 166 required := args[:sigTypes.RequiredParams()] 167 var variadic string 168 if len(args) == sigTypes.RequiredParams() { 169 // If no variadic parameters were passed, the slice value defaults to nil. 170 variadic = fmt.Sprintf("%s.nil", fc.typeName(sigTypes.VariadicType())) 171 } else { 172 variadic = fmt.Sprintf("new %s([%s])", fc.typeName(sigTypes.VariadicType()), strings.Join(args[sigTypes.RequiredParams():], ", ")) 173 } 174 return append(required, variadic) 175 } 176 return args 177 } 178 179 func (fc *funcContext) translateSelection(sel typesutil.Selection, pos token.Pos) ([]string, string) { 180 var fields []string 181 t := sel.Recv() 182 for _, index := range sel.Index() { 183 if ptr, isPtr := t.Underlying().(*types.Pointer); isPtr { 184 t = ptr.Elem() 185 } 186 s := t.Underlying().(*types.Struct) 187 if jsTag := getJsTag(s.Tag(index)); jsTag != "" { 188 jsFieldName := s.Field(index).Name() 189 for { 190 fields = append(fields, fieldName(s, 0)) 191 ft := s.Field(0).Type() 192 if typesutil.IsJsObject(ft) { 193 return fields, jsTag 194 } 195 ft = ft.Underlying() 196 if ptr, ok := ft.(*types.Pointer); ok { 197 ft = ptr.Elem().Underlying() 198 } 199 var ok bool 200 s, ok = ft.(*types.Struct) 201 if !ok || s.NumFields() == 0 { 202 fc.pkgCtx.errList = append(fc.pkgCtx.errList, types.Error{Fset: fc.pkgCtx.fileSet, Pos: pos, Msg: fmt.Sprintf("could not find field with type *js.Object for 'js' tag of field '%s'", jsFieldName), Soft: true}) 203 return nil, "" 204 } 205 } 206 } 207 fields = append(fields, fieldName(s, index)) 208 t = s.Field(index).Type() 209 } 210 return fields, "" 211 } 212 213 var nilObj = types.Universe.Lookup("nil") 214 215 func (fc *funcContext) zeroValue(ty types.Type) ast.Expr { 216 switch t := ty.Underlying().(type) { 217 case *types.Basic: 218 switch { 219 case isBoolean(t): 220 return fc.newConst(ty, constant.MakeBool(false)) 221 case isNumeric(t): 222 return fc.newConst(ty, constant.MakeInt64(0)) 223 case isString(t): 224 return fc.newConst(ty, constant.MakeString("")) 225 case t.Kind() == types.UnsafePointer: 226 // fall through to "nil" 227 case t.Kind() == types.UntypedNil: 228 panic("Zero value for untyped nil.") 229 default: 230 panic(fmt.Sprintf("Unhandled basic type: %v\n", t)) 231 } 232 case *types.Array, *types.Struct: 233 return fc.setType(&ast.CompositeLit{}, ty) 234 case *types.Chan, *types.Interface, *types.Map, *types.Signature, *types.Slice, *types.Pointer: 235 // fall through to "nil" 236 default: 237 panic(fmt.Sprintf("Unhandled type: %T\n", t)) 238 } 239 id := fc.newIdent("nil", ty) 240 fc.pkgCtx.Uses[id] = nilObj 241 return id 242 } 243 244 func (fc *funcContext) newConst(t types.Type, value constant.Value) ast.Expr { 245 id := &ast.Ident{} 246 fc.pkgCtx.Types[id] = types.TypeAndValue{Type: t, Value: value} 247 return id 248 } 249 250 // newLocalVariable assigns a new JavaScript variable name for the given Go 251 // local variable name. In this context "local" means "in scope of the current" 252 // functionContext. 253 func (fc *funcContext) newLocalVariable(name string) string { 254 return fc.newVariable(name, false) 255 } 256 257 // newVariable assigns a new JavaScript variable name for the given Go variable 258 // or type. 259 // 260 // If there is already a variable with the same name visible in the current 261 // function context (e.g. due to shadowing), the returned name will be suffixed 262 // with a number to prevent conflict. This is necessary because Go name 263 // resolution scopes differ from var declarations in JS. 264 // 265 // If pkgLevel is true, the variable is declared at the package level and added 266 // to this functionContext, as well as all parents, but not to the list of local 267 // variables. If false, it is added to this context only, as well as the list of 268 // local vars. 269 func (fc *funcContext) newVariable(name string, pkgLevel bool) string { 270 if name == "" { 271 panic("newVariable: empty name") 272 } 273 name = encodeIdent(name) 274 if fc.pkgCtx.minify { 275 i := 0 276 for { 277 offset := int('a') 278 if pkgLevel { 279 offset = int('A') 280 } 281 j := i 282 name = "" 283 for { 284 name = string(rune(offset+(j%26))) + name 285 j = j/26 - 1 286 if j == -1 { 287 break 288 } 289 } 290 if fc.allVars[name] == 0 { 291 break 292 } 293 i++ 294 } 295 } 296 n := fc.allVars[name] 297 fc.allVars[name] = n + 1 298 varName := name 299 if n > 0 { 300 varName = fmt.Sprintf("%s$%d", name, n) 301 } 302 303 if pkgLevel { 304 for c2 := fc.parent; c2 != nil; c2 = c2.parent { 305 c2.allVars[name] = n + 1 306 } 307 return varName 308 } 309 310 fc.localVars = append(fc.localVars, varName) 311 return varName 312 } 313 314 func (fc *funcContext) newIdent(name string, t types.Type) *ast.Ident { 315 ident := ast.NewIdent(name) 316 fc.setType(ident, t) 317 obj := types.NewVar(0, fc.pkgCtx.Pkg, name, t) 318 fc.pkgCtx.Uses[ident] = obj 319 fc.objectNames[obj] = name 320 return ident 321 } 322 323 func (fc *funcContext) newTypeIdent(name string, obj types.Object) *ast.Ident { 324 ident := ast.NewIdent(name) 325 fc.pkgCtx.Info.Uses[ident] = obj 326 return ident 327 } 328 329 func (fc *funcContext) setType(e ast.Expr, t types.Type) ast.Expr { 330 fc.pkgCtx.Types[e] = types.TypeAndValue{Type: t} 331 return e 332 } 333 334 func (fc *funcContext) pkgVar(pkg *types.Package) string { 335 if pkg == fc.pkgCtx.Pkg { 336 return "$pkg" 337 } 338 339 pkgVar, found := fc.pkgCtx.pkgVars[pkg.Path()] 340 if !found { 341 pkgVar = fmt.Sprintf(`$packages["%s"]`, pkg.Path()) 342 } 343 return pkgVar 344 } 345 346 func isVarOrConst(o types.Object) bool { 347 switch o.(type) { 348 case *types.Var, *types.Const: 349 return true 350 } 351 return false 352 } 353 354 func isPkgLevel(o types.Object) bool { 355 // Note: named types are always assigned a variable at package level to be 356 // initialized with the rest of the package types, even the types declared 357 // in a statement inside a function. 358 _, isType := o.(*types.TypeName) 359 return (o.Parent() != nil && o.Parent().Parent() == types.Universe) || isType 360 } 361 362 // assignedObjectName checks if the object has been previously assigned a name 363 // in this or one of the parent contexts. If not, found will be false. 364 func (fc *funcContext) assignedObjectName(o types.Object) (name string, found bool) { 365 if fc == nil { 366 return "", false 367 } 368 if name, found := fc.parent.assignedObjectName(o); found { 369 return name, true 370 } 371 372 name, found = fc.objectNames[o] 373 return name, found 374 } 375 376 // objectName returns a JS expression that refers to the given object. If the 377 // object hasn't been previously assigned a JS variable name, it will be 378 // allocated as needed. 379 func (fc *funcContext) objectName(o types.Object) string { 380 if isPkgLevel(o) { 381 fc.pkgCtx.dependencies[o] = true 382 383 if o.Pkg() != fc.pkgCtx.Pkg || (isVarOrConst(o) && o.Exported()) { 384 return fc.pkgVar(o.Pkg()) + "." + o.Name() 385 } 386 } 387 388 name, ok := fc.assignedObjectName(o) 389 if !ok { 390 pkgLevel := isPkgLevel(o) 391 name = fc.newVariable(o.Name(), pkgLevel) 392 if pkgLevel { 393 fc.root().objectNames[o] = name 394 } else { 395 fc.objectNames[o] = name 396 } 397 } 398 399 if v, ok := o.(*types.Var); ok && fc.pkgCtx.escapingVars[v] { 400 return name + "[0]" 401 } 402 return name 403 } 404 405 // instName returns a JS expression that refers to the provided instance of a 406 // function or type. Non-generic objects may be represented as an instance with 407 // zero type arguments. 408 func (fc *funcContext) instName(inst typeparams.Instance) string { 409 objName := fc.objectName(inst.Object) 410 if inst.IsTrivial() { 411 return objName 412 } 413 return fmt.Sprintf("%s[%d /* %v */]", objName, fc.pkgCtx.instanceSet.ID(inst), inst.TArgs) 414 } 415 416 func (fc *funcContext) varPtrName(o *types.Var) string { 417 if isPkgLevel(o) && o.Exported() { 418 return fc.pkgVar(o.Pkg()) + "." + o.Name() + "$ptr" 419 } 420 421 name, ok := fc.pkgCtx.varPtrNames[o] 422 if !ok { 423 name = fc.newVariable(o.Name()+"$ptr", isPkgLevel(o)) 424 fc.pkgCtx.varPtrNames[o] = name 425 } 426 return name 427 } 428 429 // typeName returns a JS identifier name for the given Go type. 430 // 431 // For the built-in types it returns identifiers declared in the prelude. For 432 // all user-defined or composite types it creates a unique JS identifier and 433 // will return it on all subsequent calls for the type. 434 func (fc *funcContext) typeName(ty types.Type) string { 435 switch t := ty.(type) { 436 case *types.Basic: 437 return "$" + toJavaScriptType(t) 438 case *types.Named: 439 if t.Obj().Name() == "error" { 440 return "$error" 441 } 442 inst := typeparams.Instance{Object: t.Obj()} 443 for i := 0; i < t.TypeArgs().Len(); i++ { 444 inst.TArgs = append(inst.TArgs, t.TypeArgs().At(i)) 445 } 446 return fc.instName(inst) 447 case *types.Interface: 448 if t.Empty() { 449 return "$emptyInterface" 450 } 451 } 452 453 // For anonymous composite types, generate a synthetic package-level type 454 // declaration, which will be reused for all instances of this time. This 455 // improves performance, since runtime won't have to synthesize the same type 456 // repeatedly. 457 anonType, ok := fc.pkgCtx.anonTypeMap.At(ty).(*types.TypeName) 458 if !ok { 459 fc.initArgs(ty) // cause all embedded types to be registered 460 varName := fc.newVariable(strings.ToLower(typeKind(ty)[5:])+"Type", true) 461 anonType = types.NewTypeName(token.NoPos, fc.pkgCtx.Pkg, varName, ty) // fake types.TypeName 462 fc.pkgCtx.anonTypes = append(fc.pkgCtx.anonTypes, anonType) 463 fc.pkgCtx.anonTypeMap.Set(ty, anonType) 464 } 465 fc.pkgCtx.dependencies[anonType] = true 466 return anonType.Name() 467 } 468 469 // instanceOf constructs an instance description of the object the ident is 470 // referring to. For non-generic objects, it will return a trivial instance with 471 // no type arguments. 472 func (fc *funcContext) instanceOf(ident *ast.Ident) typeparams.Instance { 473 inst := typeparams.Instance{Object: fc.pkgCtx.ObjectOf(ident)} 474 if i, ok := fc.pkgCtx.Instances[ident]; ok { 475 inst.TArgs = fc.typeResolver.SubstituteAll(i.TypeArgs) 476 } 477 return inst 478 } 479 480 // typeOf returns a type associated with the given AST expression. For types 481 // defined in terms of type parameters, it will substitute type parameters with 482 // concrete types from the current set of type arguments. 483 func (fc *funcContext) typeOf(expr ast.Expr) types.Type { 484 typ := fc.pkgCtx.TypeOf(expr) 485 // If the expression is referring to an instance of a generic type or function, 486 // we want the instantiated type. 487 if ident, ok := expr.(*ast.Ident); ok { 488 if inst, ok := fc.pkgCtx.Instances[ident]; ok { 489 typ = inst.Type 490 } 491 } 492 return fc.typeResolver.Substitute(typ) 493 } 494 495 func (fc *funcContext) selectionOf(e *ast.SelectorExpr) (typesutil.Selection, bool) { 496 if sel, ok := fc.pkgCtx.Selections[e]; ok { 497 return fc.typeResolver.SubstituteSelection(sel), true 498 } 499 if sel, ok := fc.pkgCtx.additionalSelections[e]; ok { 500 return sel, true 501 } 502 return nil, false 503 } 504 505 func (fc *funcContext) externalize(s string, t types.Type) string { 506 if typesutil.IsJsObject(t) { 507 return s 508 } 509 switch u := t.Underlying().(type) { 510 case *types.Basic: 511 if isNumeric(u) && !is64Bit(u) && !isComplex(u) { 512 return s 513 } 514 if u.Kind() == types.UntypedNil { 515 return "null" 516 } 517 } 518 return fmt.Sprintf("$externalize(%s, %s)", s, fc.typeName(t)) 519 } 520 521 func (fc *funcContext) handleEscapingVars(n ast.Node) { 522 newEscapingVars := make(map[*types.Var]bool) 523 for escaping := range fc.pkgCtx.escapingVars { 524 newEscapingVars[escaping] = true 525 } 526 fc.pkgCtx.escapingVars = newEscapingVars 527 528 var names []string 529 objs := analysis.EscapingObjects(n, fc.pkgCtx.Info.Info) 530 for _, obj := range objs { 531 names = append(names, fc.objectName(obj)) 532 fc.pkgCtx.escapingVars[obj] = true 533 } 534 sort.Strings(names) 535 for _, name := range names { 536 fc.Printf("%s = [%s];", name, name) 537 } 538 } 539 540 func fieldName(t *types.Struct, i int) string { 541 name := t.Field(i).Name() 542 if name == "_" || reservedKeywords[name] { 543 return fmt.Sprintf("%s$%d", name, i) 544 } 545 return name 546 } 547 548 func typeKind(ty types.Type) string { 549 switch t := ty.Underlying().(type) { 550 case *types.Basic: 551 return "$kind" + toJavaScriptType(t) 552 case *types.Array: 553 return "$kindArray" 554 case *types.Chan: 555 return "$kindChan" 556 case *types.Interface: 557 return "$kindInterface" 558 case *types.Map: 559 return "$kindMap" 560 case *types.Signature: 561 return "$kindFunc" 562 case *types.Slice: 563 return "$kindSlice" 564 case *types.Struct: 565 return "$kindStruct" 566 case *types.Pointer: 567 return "$kindPtr" 568 default: 569 panic(fmt.Sprintf("Unhandled type: %T\n", t)) 570 } 571 } 572 573 func toJavaScriptType(t *types.Basic) string { 574 switch t.Kind() { 575 case types.UntypedInt: 576 return "Int" 577 case types.Byte: 578 return "Uint8" 579 case types.Rune: 580 return "Int32" 581 case types.UnsafePointer: 582 return "UnsafePointer" 583 default: 584 name := t.String() 585 return strings.ToUpper(name[:1]) + name[1:] 586 } 587 } 588 589 func is64Bit(t *types.Basic) bool { 590 return t.Kind() == types.Int64 || t.Kind() == types.Uint64 591 } 592 593 func isBoolean(t *types.Basic) bool { 594 return t.Info()&types.IsBoolean != 0 595 } 596 597 func isComplex(t *types.Basic) bool { 598 return t.Info()&types.IsComplex != 0 599 } 600 601 func isFloat(t *types.Basic) bool { 602 return t.Info()&types.IsFloat != 0 603 } 604 605 func isInteger(t *types.Basic) bool { 606 return t.Info()&types.IsInteger != 0 607 } 608 609 func isNumeric(t *types.Basic) bool { 610 return t.Info()&types.IsNumeric != 0 611 } 612 613 func isString(t *types.Basic) bool { 614 return t.Info()&types.IsString != 0 615 } 616 617 func isUnsigned(t *types.Basic) bool { 618 return t.Info()&types.IsUnsigned != 0 619 } 620 621 func isBlank(expr ast.Expr) bool { 622 if expr == nil { 623 return true 624 } 625 if id, isIdent := expr.(*ast.Ident); isIdent { 626 return id.Name == "_" 627 } 628 return false 629 } 630 631 // isWrapped returns true for types that may need to be boxed to access full 632 // functionality of the Go type. 633 // 634 // For efficiency or interoperability reasons certain Go types can be represented 635 // by JavaScript values that weren't constructed by the corresponding Go type 636 // constructor. 637 // 638 // For example, consider a Go type: 639 // 640 // type SecretInt int 641 // func (_ SecretInt) String() string { return "<secret>" } 642 // 643 // func main() { 644 // var i SecretInt = 1 645 // println(i.String()) 646 // } 647 // 648 // For this example the compiler will generate code similar to the snippet below: 649 // 650 // SecretInt = $pkg.SecretInt = $newType(4, $kindInt, "main.SecretInt", true, "main", true, null); 651 // SecretInt.prototype.String = function() { 652 // return "<secret>"; 653 // }; 654 // main = function() { 655 // var i = 1; 656 // console.log(new SecretInt(i).String()); 657 // }; 658 // 659 // Note that the generated code assigns a primitive "number" value into i, and 660 // only boxes it into an object when it's necessary to access its methods. 661 func isWrapped(ty types.Type) bool { 662 switch t := ty.Underlying().(type) { 663 case *types.Basic: 664 return !is64Bit(t) && !isComplex(t) && t.Kind() != types.UntypedNil 665 case *types.Array, *types.Chan, *types.Map, *types.Signature: 666 return true 667 case *types.Pointer: 668 _, isArray := t.Elem().Underlying().(*types.Array) 669 return isArray 670 } 671 return false 672 } 673 674 func encodeString(s string) string { 675 buffer := bytes.NewBuffer(nil) 676 for _, r := range []byte(s) { 677 switch r { 678 case '\b': 679 buffer.WriteString(`\b`) 680 case '\f': 681 buffer.WriteString(`\f`) 682 case '\n': 683 buffer.WriteString(`\n`) 684 case '\r': 685 buffer.WriteString(`\r`) 686 case '\t': 687 buffer.WriteString(`\t`) 688 case '\v': 689 buffer.WriteString(`\v`) 690 case '"': 691 buffer.WriteString(`\"`) 692 case '\\': 693 buffer.WriteString(`\\`) 694 default: 695 if r < 0x20 || r > 0x7E { 696 fmt.Fprintf(buffer, `\x%02X`, r) 697 continue 698 } 699 buffer.WriteByte(r) 700 } 701 } 702 return `"` + buffer.String() + `"` 703 } 704 705 func getJsTag(tag string) string { 706 for tag != "" { 707 // skip leading space 708 i := 0 709 for i < len(tag) && tag[i] == ' ' { 710 i++ 711 } 712 tag = tag[i:] 713 if tag == "" { 714 break 715 } 716 717 // scan to colon. 718 // a space or a quote is a syntax error 719 i = 0 720 for i < len(tag) && tag[i] != ' ' && tag[i] != ':' && tag[i] != '"' { 721 i++ 722 } 723 if i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { 724 break 725 } 726 name := string(tag[:i]) 727 tag = tag[i+1:] 728 729 // scan quoted string to find value 730 i = 1 731 for i < len(tag) && tag[i] != '"' { 732 if tag[i] == '\\' { 733 i++ 734 } 735 i++ 736 } 737 if i >= len(tag) { 738 break 739 } 740 qvalue := string(tag[:i+1]) 741 tag = tag[i+1:] 742 743 if name == "js" { 744 value, _ := strconv.Unquote(qvalue) 745 return value 746 } 747 } 748 return "" 749 } 750 751 func needsSpace(c byte) bool { 752 return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '$' 753 } 754 755 func removeWhitespace(b []byte, minify bool) []byte { 756 if !minify { 757 return b 758 } 759 760 var out []byte 761 var previous byte 762 for len(b) > 0 { 763 switch b[0] { 764 case '\b': 765 out = append(out, b[:5]...) 766 b = b[5:] 767 continue 768 case ' ', '\t', '\n': 769 if (!needsSpace(previous) || !needsSpace(b[1])) && !(previous == '-' && b[1] == '-') { 770 b = b[1:] 771 continue 772 } 773 case '"': 774 out = append(out, '"') 775 b = b[1:] 776 for { 777 i := bytes.IndexAny(b, "\"\\") 778 out = append(out, b[:i]...) 779 b = b[i:] 780 if b[0] == '"' { 781 break 782 } 783 // backslash 784 out = append(out, b[:2]...) 785 b = b[2:] 786 } 787 case '/': 788 if b[1] == '*' { 789 i := bytes.Index(b[2:], []byte("*/")) 790 b = b[i+4:] 791 continue 792 } 793 } 794 out = append(out, b[0]) 795 previous = b[0] 796 b = b[1:] 797 } 798 return out 799 } 800 801 func rangeCheck(pattern string, constantIndex, array bool) string { 802 if constantIndex && array { 803 return pattern 804 } 805 lengthProp := "$length" 806 if array { 807 lengthProp = "length" 808 } 809 check := "%2f >= %1e." + lengthProp 810 if !constantIndex { 811 check = "(%2f < 0 || " + check + ")" 812 } 813 return "(" + check + ` ? ($throwRuntimeError("index out of range"), undefined) : ` + pattern + ")" 814 } 815 816 func encodeIdent(name string) string { 817 return strings.Replace(url.QueryEscape(name), "%", "$", -1) 818 } 819 820 // formatJSStructTagVal returns JavaScript code for accessing an object's property 821 // identified by jsTag. It prefers the dot notation over the bracket notation when 822 // possible, since the dot notation produces slightly smaller output. 823 // 824 // For example: 825 // 826 // "my_name" -> ".my_name" 827 // "my name" -> `["my name"]` 828 // 829 // For more information about JavaScript property accessors and identifiers, see 830 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_Accessors and 831 // https://developer.mozilla.org/en-US/docs/Glossary/Identifier. 832 func formatJSStructTagVal(jsTag string) string { 833 for i, r := range jsTag { 834 ok := unicode.IsLetter(r) || (i != 0 && unicode.IsNumber(r)) || r == '$' || r == '_' 835 if !ok { 836 // Saw an invalid JavaScript identifier character, 837 // so use bracket notation. 838 return `["` + template.JSEscapeString(jsTag) + `"]` 839 } 840 } 841 // Safe to use dot notation without any escaping. 842 return "." + jsTag 843 } 844 845 // ErrorAt annotates an error with a position in the source code. 846 func ErrorAt(err error, fset *token.FileSet, pos token.Pos) error { 847 return fmt.Errorf("%s: %w", fset.Position(pos), err) 848 } 849 850 // FatalError is an error compiler panics with when it encountered a fatal error. 851 // 852 // FatalError implements io.Writer, which can be used to record any free-form 853 // debugging details for human consumption. This information will be included 854 // into String() result along with the rest. 855 type FatalError struct { 856 cause interface{} 857 stack []byte 858 clues strings.Builder 859 } 860 861 func (b FatalError) Unwrap() error { 862 if b.cause == nil { 863 return nil 864 } 865 if err, ok := b.cause.(error); ok { 866 return err 867 } 868 if s, ok := b.cause.(string); ok { 869 return errors.New(s) 870 } 871 return fmt.Errorf("[%T]: %v", b.cause, b.cause) 872 } 873 874 // Write implements io.Writer and can be used to store free-form debugging clues. 875 func (b *FatalError) Write(p []byte) (n int, err error) { return b.clues.Write(p) } 876 877 func (b FatalError) Error() string { 878 buf := &strings.Builder{} 879 fmt.Fprintln(buf, "[compiler panic] ", strings.TrimSpace(b.Unwrap().Error())) 880 if b.clues.Len() > 0 { 881 fmt.Fprintln(buf, "\n"+b.clues.String()) 882 } 883 if len(b.stack) > 0 { 884 // Shift stack track by 2 spaces for better readability. 885 stack := regexp.MustCompile("(?m)^").ReplaceAll(b.stack, []byte(" ")) 886 fmt.Fprintln(buf, "\nOriginal stack trace:\n", string(stack)) 887 } 888 return buf.String() 889 } 890 891 func bailout(cause interface{}) *FatalError { 892 b := &FatalError{ 893 cause: cause, 894 stack: debug.Stack(), 895 } 896 return b 897 } 898 899 func bailingOut(err interface{}) (*FatalError, bool) { 900 fe, ok := err.(*FatalError) 901 return fe, ok 902 }