internal/lsp: expose godoc or pkg.go.dev link on hover

This adds a link to documentation to the hover contents for the
current symbol if it is exported.

Updates golang/go#34240

Change-Id: I19c66e91e46f79284bfd0006c53f518eda4edef7
Reviewed-on: https://go-review.googlesource.com/c/tools/+/200604
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
diff --git a/internal/lsp/cmd/test/imports.go b/internal/lsp/cmd/test/imports.go
index c5b1a50..03d7ecc 100644
--- a/internal/lsp/cmd/test/imports.go
+++ b/internal/lsp/cmd/test/imports.go
@@ -21,6 +21,6 @@
 		return out, nil
 	}))
 	if want != got {
-		t.Errorf("imports failed for %s, expected:\n%v\ngot:\n%v", filename, want, got)
+		t.Errorf("imports failed for %s, expected:\n%q\ngot:\n%q", filename, want, got)
 	}
 }
diff --git a/internal/lsp/hover.go b/internal/lsp/hover.go
index 11aad43..7334d92 100644
--- a/internal/lsp/hover.go
+++ b/internal/lsp/hover.go
@@ -73,6 +73,7 @@
 		} else {
 			content.Value = signature
 		}
+		content.Value += "\n" + h.DocumentationLink(options)
 	case source.FullDocumentation:
 		if h.FullDocumentation != "" {
 			doc := h.FullDocumentation
@@ -83,6 +84,7 @@
 		} else {
 			content.Value = signature
 		}
+		content.Value += "\n" + h.DocumentationLink(options)
 	case source.Structured:
 		b, err := json.Marshal(h)
 		if err != nil {
diff --git a/internal/lsp/source/hover.go b/internal/lsp/source/hover.go
index a3066c1..54b1070 100644
--- a/internal/lsp/source/hover.go
+++ b/internal/lsp/source/hover.go
@@ -31,10 +31,23 @@
 	// FullDocumentation is the symbol's full documentation.
 	FullDocumentation string `json:"fullDocumentation"`
 
+	// pkgPath holds the package path of the hovered symbol.
+	pkgPath string
+
+	// symbolName holds the symbol name without any package prefix.
+	symbolName string
+
 	source  interface{}
 	comment *ast.CommentGroup
 }
 
+func (h *HoverInformation) DocumentationLink(options Options) string {
+	if h.symbolName == "" || h.pkgPath == "" || options.LinkTarget == "" {
+		return ""
+	}
+	return fmt.Sprintf("[%s on %s](https://%s/%s#%s)", h.symbolName, options.LinkTarget, options.LinkTarget, h.pkgPath, h.symbolName)
+}
+
 func (i *IdentifierInfo) Hover(ctx context.Context) (*HoverInformation, error) {
 	ctx, done := trace.StartSpan(ctx, "source.Hover")
 	defer done()
@@ -54,11 +67,17 @@
 	case types.Object:
 		h.Signature = objectString(x, i.qf)
 	}
-
-	// Set the documentation.
-	if i.Declaration.obj != nil {
-		h.SingleLine = objectString(i.Declaration.obj, i.qf)
+	if obj := i.Declaration.obj; obj != nil {
+		// Only show the documentation links for symbols in the package scope.
+		// TODO(https://golang.org/issue/34240): Handle other symbols.
+		if obj.Exported() && obj.Parent() == obj.Pkg().Scope() {
+			h.pkgPath = obj.Pkg().Path()
+			h.symbolName = obj.Name()
+		}
+		// Set the documentation.
+		h.SingleLine = objectString(obj, i.qf)
 	}
+
 	if h.comment != nil {
 		h.FullDocumentation = h.comment.Text()
 		h.Synopsis = doc.Synopsis(h.FullDocumentation)
diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go
index 98daac2..48dda36 100644
--- a/internal/lsp/source/source_test.go
+++ b/internal/lsp/source/source_test.go
@@ -512,6 +512,7 @@
 		hover += h.Synopsis + "\n"
 	}
 	hover += h.Signature
+	hover += "\n" + h.DocumentationLink(r.view.Options())
 	rng, err := ident.Declaration.Range()
 	if err != nil {
 		t.Fatal(err)
diff --git a/internal/lsp/testdata/cgoimport/usecgo.go.golden b/internal/lsp/testdata/cgoimport/usecgo.go.golden
index e5ba830..27b66ad 100644
--- a/internal/lsp/testdata/cgoimport/usecgo.go.golden
+++ b/internal/lsp/testdata/cgoimport/usecgo.go.golden
@@ -1,5 +1,6 @@
 -- funccgoexample-definition --
 cgo/declarecgo.go:17:6-13: defined here as func cgo.Example()
+[Example on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/cgo#Example)
 -- funccgoexample-definition-json --
 {
 	"span": {
@@ -15,8 +16,9 @@
 			"offset": 160
 		}
 	},
-	"description": "func cgo.Example()"
+	"description": "func cgo.Example()\n[Example on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/cgo#Example)"
 }
 
 -- funccgoexample-hover --
 func cgo.Example()
+[Example on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/cgo#Example)
diff --git a/internal/lsp/testdata/godef/a/a.go.golden b/internal/lsp/testdata/godef/a/a.go.golden
index 20853fe..260c44b 100644
--- a/internal/lsp/testdata/godef/a/a.go.golden
+++ b/internal/lsp/testdata/godef/a/a.go.golden
@@ -1,5 +1,6 @@
 -- Random-definition --
 godef/a/random.go:3:6-12: defined here as func Random() int
+[Random on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Random)
 -- Random-definition-json --
 {
 	"span": {
@@ -15,13 +16,15 @@
 			"offset": 22
 		}
 	},
-	"description": "func Random() int"
+	"description": "func Random() int\n[Random on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Random)"
 }
 
 -- Random-hover --
 func Random() int
+[Random on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Random)
 -- Random2-definition --
 godef/a/random.go:8:6-13: defined here as func Random2(y int) int
+[Random2 on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Random2)
 -- Random2-definition-json --
 {
 	"span": {
@@ -37,11 +40,12 @@
 			"offset": 78
 		}
 	},
-	"description": "func Random2(y int) int"
+	"description": "func Random2(y int) int\n[Random2 on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Random2)"
 }
 
 -- Random2-hover --
 func Random2(y int) int
+[Random2 on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Random2)
 -- err-definition --
 godef/a/a.go:14:6-9: defined here as var err error
 -- err-definition-json --
@@ -64,8 +68,11 @@
 
 -- err-hover --
 var err error
+
 -- make-hover --
 The make built-in function allocates and initializes an object of type slice, map, or chan (only).
 func(t Type, size ...IntegerType) Type
+
 -- string-hover --
 string
+
diff --git a/internal/lsp/testdata/godef/a/d.go.golden b/internal/lsp/testdata/godef/a/d.go.golden
index a421d04..e048ff9 100644
--- a/internal/lsp/testdata/godef/a/d.go.golden
+++ b/internal/lsp/testdata/godef/a/d.go.golden
@@ -22,6 +22,7 @@
 -- Member-hover --
 @Member
 field Member string
+
 -- Method-definition --
 godef/a/d.go:15:16-22: defined here as func (Thing).Method(i int) string
 -- Method-definition-json --
@@ -44,8 +45,10 @@
 
 -- Method-hover --
 func (Thing).Method(i int) string
+
 -- Other-definition --
 godef/a/d.go:9:5-10: defined here as var Other Thing
+[Other on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Other)
 -- Other-definition-json --
 {
 	"span": {
@@ -61,15 +64,17 @@
 			"offset": 91
 		}
 	},
-	"description": "var Other Thing"
+	"description": "var Other Thing\n[Other on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Other)"
 }
 
 -- Other-hover --
 var Other Thing
+[Other on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Other)
 -- Thing-definition --
 godef/a/d.go:5:6-11: defined here as Thing struct {
 	Member string //@Member
 }
+[Thing on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing)
 -- Thing-definition-json --
 {
 	"span": {
@@ -85,15 +90,17 @@
 			"offset": 35
 		}
 	},
-	"description": "Thing struct {\n\tMember string //@Member\n}"
+	"description": "Thing struct {\n\tMember string //@Member\n}\n[Thing on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing)"
 }
 
 -- Thing-hover --
 Thing struct {
 	Member string //@Member
 }
+[Thing on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing)
 -- Things-definition --
 godef/a/d.go:11:6-12: defined here as func Things(val []string) []Thing
+[Things on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Things)
 -- Things-definition-json --
 {
 	"span": {
@@ -109,8 +116,9 @@
 			"offset": 119
 		}
 	},
-	"description": "func Things(val []string) []Thing"
+	"description": "func Things(val []string) []Thing\n[Things on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Things)"
 }
 
 -- Things-hover --
 func Things(val []string) []Thing
+[Things on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Things)
diff --git a/internal/lsp/testdata/godef/a/f.go.golden b/internal/lsp/testdata/godef/a/f.go.golden
index 17574b8..bdf4b99 100644
--- a/internal/lsp/testdata/godef/a/f.go.golden
+++ b/internal/lsp/testdata/godef/a/f.go.golden
@@ -20,6 +20,7 @@
 
 -- switchStringY-hover --
 var y string
+
 -- switchY-definition --
 godef/a/f.go:8:9-10: defined here as var y int
 -- switchY-definition-json --
@@ -42,3 +43,4 @@
 
 -- switchY-hover --
 var y int
+
diff --git a/internal/lsp/testdata/godef/a/random.go.golden b/internal/lsp/testdata/godef/a/random.go.golden
index d215bc8..502e93a 100644
--- a/internal/lsp/testdata/godef/a/random.go.golden
+++ b/internal/lsp/testdata/godef/a/random.go.golden
@@ -20,6 +20,7 @@
 
 -- PosSum-hover --
 func (*Pos).Sum() int
+
 -- PosX-definition --
 godef/a/random.go:13:2-3: defined here as @mark(PosX, "x"),mark(PosY, "y")
 field x int
@@ -44,6 +45,7 @@
 -- PosX-hover --
 @mark(PosX, "x"),mark(PosY, "y")
 field x int
+
 -- RandomParamY-definition --
 godef/a/random.go:8:14-15: defined here as var y int
 -- RandomParamY-definition-json --
@@ -66,3 +68,4 @@
 
 -- RandomParamY-hover --
 var y int
+
diff --git a/internal/lsp/testdata/godef/b/b.go.golden b/internal/lsp/testdata/godef/b/b.go.golden
index 276f188..3869da5 100644
--- a/internal/lsp/testdata/godef/b/b.go.golden
+++ b/internal/lsp/testdata/godef/b/b.go.golden
@@ -1,5 +1,7 @@
 -- A-definition --
 godef/a/a.go:7:6-7: defined here as A string //@A
+
+[A on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#A)
 -- A-definition-json --
 {
 	"span": {
@@ -15,12 +17,13 @@
 			"offset": 76
 		}
 	},
-	"description": "A string //@A"
+	"description": "A string //@A\n\n[A on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#A)"
 }
 
 -- A-hover --
 A string //@A
 
+[A on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#A)
 -- AImport-definition --
 godef/b/b.go:5:2-43: defined here as package a ("golang.org/x/tools/internal/lsp/godef/a")
 -- AImport-definition-json --
@@ -43,8 +46,10 @@
 
 -- AImport-hover --
 package a ("golang.org/x/tools/internal/lsp/godef/a")
+
 -- AStuff-definition --
 godef/a/a.go:9:6-12: defined here as func a.AStuff()
+[AStuff on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#AStuff)
 -- AStuff-definition-json --
 {
 	"span": {
@@ -60,11 +65,12 @@
 			"offset": 101
 		}
 	},
-	"description": "func a.AStuff()"
+	"description": "func a.AStuff()\n[AStuff on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#AStuff)"
 }
 
 -- AStuff-hover --
 func a.AStuff()
+[AStuff on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#AStuff)
 -- PackageFoo-definition --
 foo/foo.go:1:1-30:16: defined here as myFoo "golang.org/x/tools/internal/lsp/foo" //@mark(myFoo, "myFoo"),godef("foo", PackageFoo),godef("myFoo", myFoo)
 -- PackageFoo-definition-json --
@@ -94,6 +100,7 @@
 	S2      //@godef("S2", S2), mark(S1S2, "S2")
 	a.A     //@godef("A", A)
 }
+[S1 on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)
 -- S1-definition-json --
 {
 	"span": {
@@ -109,7 +116,7 @@
 			"offset": 195
 		}
 	},
-	"description": "S1 struct {\n\tF1  int //@mark(S1F1, \"F1\")\n\tS2      //@godef(\"S2\", S2), mark(S1S2, \"S2\")\n\ta.A     //@godef(\"A\", A)\n}"
+	"description": "S1 struct {\n\tF1  int //@mark(S1F1, \"F1\")\n\tS2      //@godef(\"S2\", S2), mark(S1S2, \"S2\")\n\ta.A     //@godef(\"A\", A)\n}\n[S1 on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)"
 }
 
 -- S1-hover --
@@ -118,6 +125,7 @@
 	S2      //@godef("S2", S2), mark(S1S2, "S2")
 	a.A     //@godef("A", A)
 }
+[S1 on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)
 -- S1F1-definition --
 godef/b/b.go:9:2-4: defined here as @mark(S1F1, "F1")
 field F1 int
@@ -142,6 +150,7 @@
 -- S1F1-hover --
 @mark(S1F1, "F1")
 field F1 int
+
 -- S1S2-definition --
 godef/b/b.go:10:2-4: defined here as @godef("S2", S2), mark(S1S2, "S2")
 field S2 S2
@@ -166,12 +175,14 @@
 -- S1S2-hover --
 @godef("S2", S2), mark(S1S2, "S2")
 field S2 S2
+
 -- S2-definition --
 godef/b/b.go:14:6-8: defined here as S2 struct {
 	F1   string //@mark(S2F1, "F1")
 	F2   int    //@mark(S2F2, "F2")
 	*a.A        //@godef("A", A),godef("a",AImport)
 }
+[S2 on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2)
 -- S2-definition-json --
 {
 	"span": {
@@ -187,7 +198,7 @@
 			"offset": 322
 		}
 	},
-	"description": "S2 struct {\n\tF1   string //@mark(S2F1, \"F1\")\n\tF2   int    //@mark(S2F2, \"F2\")\n\t*a.A        //@godef(\"A\", A),godef(\"a\",AImport)\n}"
+	"description": "S2 struct {\n\tF1   string //@mark(S2F1, \"F1\")\n\tF2   int    //@mark(S2F2, \"F2\")\n\t*a.A        //@godef(\"A\", A),godef(\"a\",AImport)\n}\n[S2 on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2)"
 }
 
 -- S2-hover --
@@ -196,6 +207,7 @@
 	F2   int    //@mark(S2F2, "F2")
 	*a.A        //@godef("A", A),godef("a",AImport)
 }
+[S2 on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2)
 -- S2F1-definition --
 godef/b/b.go:15:2-4: defined here as @mark(S2F1, "F1")
 field F1 string
@@ -220,6 +232,7 @@
 -- S2F1-hover --
 @mark(S2F1, "F1")
 field F1 string
+
 -- S2F2-definition --
 godef/b/b.go:16:2-4: defined here as @mark(S2F2, "F2")
 field F2 int
@@ -244,6 +257,7 @@
 -- S2F2-hover --
 @mark(S2F2, "F2")
 field F2 int
+
 -- Stuff-definition --
 godef/a/a.go:9:6-11: defined here as func a.Stuff()
 -- Stuff-definition-json --
@@ -268,6 +282,7 @@
 func a.Stuff()
 -- X-definition --
 godef/b/b.go:37:7-8: defined here as const X untyped int = 0
+[X on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#X)
 -- X-definition-json --
 {
 	"span": {
@@ -283,11 +298,12 @@
 			"offset": 796
 		}
 	},
-	"description": "const X untyped int = 0"
+	"description": "const X untyped int = 0\n[X on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#X)"
 }
 
 -- X-hover --
 const X untyped int = 0
+[X on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#X)
 -- myFoo-definition --
 godef/b/b.go:4:2-7: defined here as package myFoo ("golang.org/x/tools/internal/lsp/foo")
 -- myFoo-definition-json --
@@ -310,3 +326,4 @@
 
 -- myFoo-hover --
 package myFoo ("golang.org/x/tools/internal/lsp/foo")
+
diff --git a/internal/lsp/testdata/godef/b/c.go.golden b/internal/lsp/testdata/godef/b/c.go.golden
index c2ab4a2..c5f2741 100644
--- a/internal/lsp/testdata/godef/b/c.go.golden
+++ b/internal/lsp/testdata/godef/b/c.go.golden
@@ -4,6 +4,7 @@
 	S2      //@godef("S2", S2), mark(S1S2, "S2")
 	a.A     //@godef("A", A)
 }
+[S1 on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)
 -- S1-definition-json --
 {
 	"span": {
@@ -19,7 +20,7 @@
 			"offset": 195
 		}
 	},
-	"description": "S1 struct {\n\tF1  int //@mark(S1F1, \"F1\")\n\tS2      //@godef(\"S2\", S2), mark(S1S2, \"S2\")\n\ta.A     //@godef(\"A\", A)\n}"
+	"description": "S1 struct {\n\tF1  int //@mark(S1F1, \"F1\")\n\tS2      //@godef(\"S2\", S2), mark(S1S2, \"S2\")\n\ta.A     //@godef(\"A\", A)\n}\n[S1 on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)"
 }
 
 -- S1-hover --
@@ -28,6 +29,7 @@
 	S2      //@godef("S2", S2), mark(S1S2, "S2")
 	a.A     //@godef("A", A)
 }
+[S1 on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)
 -- S1F1-definition --
 godef/b/b.go:9:2-4: defined here as @mark(S1F1, "F1")
 field F1 int
@@ -52,3 +54,4 @@
 -- S1F1-hover --
 @mark(S1F1, "F1")
 field F1 int
+
diff --git a/internal/lsp/testdata/godef/b/e.go.golden b/internal/lsp/testdata/godef/b/e.go.golden
index 9479ea2..074fce3 100644
--- a/internal/lsp/testdata/godef/b/e.go.golden
+++ b/internal/lsp/testdata/godef/b/e.go.golden
@@ -22,8 +22,10 @@
 -- Member-hover --
 @Member
 field Member string
+
 -- Other-definition --
 godef/a/d.go:9:5-10: defined here as var a.Other a.Thing
+[Other on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Other)
 -- Other-definition-json --
 {
 	"span": {
@@ -39,15 +41,17 @@
 			"offset": 91
 		}
 	},
-	"description": "var a.Other a.Thing"
+	"description": "var a.Other a.Thing\n[Other on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Other)"
 }
 
 -- Other-hover --
 var a.Other a.Thing
+[Other on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Other)
 -- Thing-definition --
 godef/a/d.go:5:6-11: defined here as Thing struct {
 	Member string //@Member
 }
+[Thing on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing)
 -- Thing-definition-json --
 {
 	"span": {
@@ -63,15 +67,17 @@
 			"offset": 35
 		}
 	},
-	"description": "Thing struct {\n\tMember string //@Member\n}"
+	"description": "Thing struct {\n\tMember string //@Member\n}\n[Thing on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing)"
 }
 
 -- Thing-hover --
 Thing struct {
 	Member string //@Member
 }
+[Thing on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing)
 -- Things-definition --
 godef/a/d.go:11:6-12: defined here as func a.Things(val []string) []a.Thing
+[Things on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Things)
 -- Things-definition-json --
 {
 	"span": {
@@ -87,8 +93,9 @@
 			"offset": 119
 		}
 	},
-	"description": "func a.Things(val []string) []a.Thing"
+	"description": "func a.Things(val []string) []a.Thing\n[Things on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Things)"
 }
 
 -- Things-hover --
 func a.Things(val []string) []a.Thing
+[Things on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Things)
diff --git a/internal/lsp/testdata/godef/broken/unclosedIf.go.golden b/internal/lsp/testdata/godef/broken/unclosedIf.go.golden
index c546e26..822aab0 100644
--- a/internal/lsp/testdata/godef/broken/unclosedIf.go.golden
+++ b/internal/lsp/testdata/godef/broken/unclosedIf.go.golden
@@ -20,3 +20,4 @@
 
 -- myUnclosedIf-hover --
 var myUnclosedIf string
+