|  | // Copyright 2021 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | package misc | 
|  |  | 
|  | import ( | 
|  | "strings" | 
|  | "testing" | 
|  |  | 
|  | "github.com/google/go-cmp/cmp" | 
|  | "golang.org/x/tools/gopls/internal/lsp" | 
|  | "golang.org/x/tools/gopls/internal/lsp/protocol" | 
|  | . "golang.org/x/tools/gopls/internal/lsp/regtest" | 
|  | "golang.org/x/tools/internal/typeparams" | 
|  | ) | 
|  |  | 
|  | func TestBadURICrash_VSCodeIssue1498(t *testing.T) { | 
|  | const src = ` | 
|  | -- go.mod -- | 
|  | module example.com | 
|  |  | 
|  | go 1.12 | 
|  |  | 
|  | -- main.go -- | 
|  | package main | 
|  |  | 
|  | func main() {} | 
|  |  | 
|  | ` | 
|  | WithOptions( | 
|  | Modes(Default), | 
|  | Settings{"allExperiments": true}, | 
|  | ).Run(t, src, func(t *testing.T, env *Env) { | 
|  | params := &protocol.SemanticTokensParams{} | 
|  | const badURI = "http://foo" | 
|  | params.TextDocument.URI = badURI | 
|  | // This call panicked in the past: golang/vscode-go#1498. | 
|  | if _, err := env.Editor.Server.SemanticTokensFull(env.Ctx, params); err != nil { | 
|  | // Requests to an invalid URI scheme shouldn't result in an error, we | 
|  | // simply don't support this so return empty result. This could be | 
|  | // changed, but for now assert on the current behavior. | 
|  | t.Errorf("SemanticTokensFull(%q): %v", badURI, err) | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | // fix bug involving type parameters and regular parameters | 
|  | // (golang/vscode-go#2527) | 
|  | func TestSemantic_2527(t *testing.T) { | 
|  | if !typeparams.Enabled { | 
|  | t.Skip("type parameters are needed for this test") | 
|  | } | 
|  | // these are the expected types of identfiers in textt order | 
|  | want := []result{ | 
|  | {"package", "keyword", ""}, | 
|  | {"foo", "namespace", ""}, | 
|  | {"func", "keyword", ""}, | 
|  | {"Add", "function", "definition deprecated"}, | 
|  | {"T", "typeParameter", "definition"}, | 
|  | {"int", "type", "defaultLibrary"}, | 
|  | {"target", "parameter", "definition"}, | 
|  | {"T", "typeParameter", ""}, | 
|  | {"l", "parameter", "definition"}, | 
|  | {"T", "typeParameter", ""}, | 
|  | {"T", "typeParameter", ""}, | 
|  | {"return", "keyword", ""}, | 
|  | {"append", "function", "defaultLibrary"}, | 
|  | {"l", "parameter", ""}, | 
|  | {"target", "parameter", ""}, | 
|  | {"for", "keyword", ""}, | 
|  | {"range", "keyword", ""}, | 
|  | {"l", "parameter", ""}, | 
|  | {"return", "keyword", ""}, | 
|  | {"nil", "variable", "readonly defaultLibrary"}, | 
|  | } | 
|  | src := ` | 
|  | -- go.mod -- | 
|  | module example.com | 
|  |  | 
|  | go 1.19 | 
|  | -- main.go -- | 
|  | package foo | 
|  | // Deprecated (for testing) | 
|  | func Add[T int](target T, l []T) []T { | 
|  | return append(l, target) | 
|  | for range l {} // test coverage | 
|  | return nil | 
|  | } | 
|  | ` | 
|  | WithOptions( | 
|  | Modes(Default), | 
|  | Settings{"semanticTokens": true}, | 
|  | ).Run(t, src, func(t *testing.T, env *Env) { | 
|  | env.OpenFile("main.go") | 
|  | env.AfterChange(env.DiagnosticAtRegexp("main.go", "for range")) | 
|  | p := &protocol.SemanticTokensParams{ | 
|  | TextDocument: protocol.TextDocumentIdentifier{ | 
|  | URI: env.Sandbox.Workdir.URI("main.go"), | 
|  | }, | 
|  | } | 
|  | v, err := env.Editor.Server.SemanticTokensFull(env.Ctx, p) | 
|  | if err != nil { | 
|  | t.Fatal(err) | 
|  | } | 
|  | seen := interpret(v.Data, env.Editor.BufferText("main.go")) | 
|  | if x := cmp.Diff(want, seen); x != "" { | 
|  | t.Errorf("Semantic tokens do not match (-want +got):\n%s", x) | 
|  | } | 
|  | }) | 
|  |  | 
|  | } | 
|  |  | 
|  | type result struct { | 
|  | Token     string | 
|  | TokenType string | 
|  | Mod       string | 
|  | } | 
|  |  | 
|  | // human-readable version of the semantic tokens | 
|  | // comment, string, number are elided | 
|  | // (and in the future, maybe elide other things, like operators) | 
|  | func interpret(x []uint32, contents string) []result { | 
|  | lines := strings.Split(contents, "\n") | 
|  | ans := []result{} | 
|  | line, col := 1, 1 | 
|  | for i := 0; i < len(x); i += 5 { | 
|  | line += int(x[i]) | 
|  | col += int(x[i+1]) | 
|  | if x[i] != 0 { // new line | 
|  | col = int(x[i+1]) + 1 // 1-based column numbers | 
|  | } | 
|  | sz := x[i+2] | 
|  | t := semanticTypes[x[i+3]] | 
|  | if t == "comment" || t == "string" || t == "number" { | 
|  | continue | 
|  | } | 
|  | l := x[i+4] | 
|  | var mods []string | 
|  | for i, mod := range semanticModifiers { | 
|  | if l&(1<<i) != 0 { | 
|  | mods = append(mods, mod) | 
|  | } | 
|  | } | 
|  | // col is a utf-8 offset | 
|  | tok := lines[line-1][col-1 : col-1+int(sz)] | 
|  | ans = append(ans, result{tok, t, strings.Join(mods, " ")}) | 
|  | } | 
|  | return ans | 
|  | } | 
|  |  | 
|  | var ( | 
|  | semanticTypes     = lsp.SemanticTypes() | 
|  | semanticModifiers = lsp.SemanticModifiers() | 
|  | ) |