whatcanGOwrong
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,150 @@
|
||||
# `gopls`, the Go language server
|
||||
|
||||
[](https://pkg.go.dev/golang.org/x/tools/gopls)
|
||||
|
||||
`gopls` (pronounced "Go please") is the official Go [language server] developed
|
||||
by the Go team. It provides IDE features to any [LSP]-compatible editor.
|
||||
|
||||
<!--TODO(rfindley): Add gifs here.-->
|
||||
|
||||
You should not need to interact with `gopls` directly--it will be automatically
|
||||
integrated into your editor. The specific features and settings vary slightly
|
||||
by editor, so we recommend that you proceed to the
|
||||
[documentation for your editor](#editors) below.
|
||||
|
||||
## Editors
|
||||
|
||||
To get started with `gopls`, install an LSP plugin in your editor of choice.
|
||||
|
||||
* [VS Code](https://github.com/golang/vscode-go/blob/master/README.md)
|
||||
* [Vim / Neovim](doc/vim.md)
|
||||
* [Emacs](doc/emacs.md)
|
||||
* [Atom](https://github.com/MordFustang21/ide-gopls)
|
||||
* [Sublime Text](doc/subl.md)
|
||||
* [Acme](https://github.com/fhs/acme-lsp)
|
||||
* [Lapce](https://github.com/lapce-community/lapce-go)
|
||||
|
||||
If you use `gopls` with an editor that is not on this list, please send us a CL
|
||||
[updating this documentation](doc/contributing.md).
|
||||
|
||||
## Installation
|
||||
|
||||
For the most part, you should not need to install or update `gopls`. Your
|
||||
editor should handle that step for you.
|
||||
|
||||
If you do want to get the latest stable version of `gopls`, run the following
|
||||
command:
|
||||
|
||||
```sh
|
||||
go install golang.org/x/tools/gopls@latest
|
||||
```
|
||||
|
||||
Learn more in the
|
||||
[advanced installation instructions](doc/advanced.md#installing-unreleased-versions).
|
||||
|
||||
Learn more about gopls releases in the [release policy](doc/releases.md).
|
||||
|
||||
## Setting up your workspace
|
||||
|
||||
`gopls` supports both Go module, multi-module and GOPATH modes. See the
|
||||
[workspace documentation](doc/workspace.md) for information on supported
|
||||
workspace layouts.
|
||||
|
||||
## Configuration
|
||||
|
||||
You can configure `gopls` to change your editor experience or view additional
|
||||
debugging information. Configuration options will be made available by your
|
||||
editor, so see your [editor's instructions](#editors) for specific details. A
|
||||
full list of `gopls` settings can be found in the [settings documentation](doc/settings.md).
|
||||
|
||||
### Environment variables
|
||||
|
||||
`gopls` inherits your editor's environment, so be aware of any environment
|
||||
variables you configure. Some editors, such as VS Code, allow users to
|
||||
selectively override the values of some environment variables.
|
||||
|
||||
## Support Policy
|
||||
|
||||
Gopls is maintained by engineers on the
|
||||
[Go tools team](https://github.com/orgs/golang/teams/tools-team/members),
|
||||
who actively monitor the
|
||||
[Go](https://github.com/golang/go/issues?q=is%3Aissue+is%3Aopen+label%3Agopls)
|
||||
and
|
||||
[VS Code Go](https://github.com/golang/vscode-go/issues) issue trackers.
|
||||
|
||||
### Supported Go versions
|
||||
|
||||
`gopls` follows the
|
||||
[Go Release Policy](https://golang.org/doc/devel/release.html#policy), meaning
|
||||
that it officially supports only the two most recent major Go releases. Until
|
||||
August 2024, the Go team will also maintain best-effort support for the last
|
||||
4 major Go releases, as described in [issue #39146](https://go.dev/issues/39146).
|
||||
|
||||
When using gopls, there are three versions to be aware of:
|
||||
1. The _gopls build go version_: the version of Go used to build gopls.
|
||||
2. The _go command version_: the version of the go list command executed by
|
||||
gopls to load information about your workspace.
|
||||
3. The _language version_: the version in the go directive of the current
|
||||
file's enclosing go.mod file, which determines the file's Go language
|
||||
semantics.
|
||||
|
||||
Starting with the release of Go 1.23.0 and gopls@v0.17.0 in August 2024, we
|
||||
will only support the most recent Go version as the _gopls build go version_.
|
||||
However, due to the [forward compatibility](https://go.dev/blog/toolchain)
|
||||
support added in Go 1.21, as long as Go 1.21 or later are used to install
|
||||
gopls, any necessary toolchain upgrade will be handled automatically, just like
|
||||
any other dependency.
|
||||
|
||||
Additionally, starting with gopls@v0.17.0, the _go command version_ will narrow
|
||||
from 4 versions to 3. This is more consistent with the Go Release Policy.
|
||||
|
||||
Gopls supports **all** Go versions as its _language version_, by providing
|
||||
compiler errors based on the language version and filtering available standard
|
||||
library symbols based on the standard library APIs available at that Go
|
||||
version.
|
||||
|
||||
Maintaining support for building gopls with legacy versions of Go caused
|
||||
[significant friction](https://go.dev/issue/50825) for gopls maintainers and
|
||||
held back other improvements. If you are unable to install a supported version
|
||||
of Go on your system, you can still install an older version of gopls. The
|
||||
following table shows the final gopls version that supports a given Go version.
|
||||
Go releases more recent than those in the table can be used with any version of
|
||||
gopls.
|
||||
|
||||
| Go Version | Final gopls version with support (without warnings) |
|
||||
| ----------- | --------------------------------------------------- |
|
||||
| Go 1.12 | [gopls@v0.7.5](https://github.com/golang/tools/releases/tag/gopls%2Fv0.7.5) |
|
||||
| Go 1.15 | [gopls@v0.9.5](https://github.com/golang/tools/releases/tag/gopls%2Fv0.9.5) |
|
||||
| Go 1.17 | [gopls@v0.11.0](https://github.com/golang/tools/releases/tag/gopls%2Fv0.11.0) |
|
||||
| Go 1.18 | [gopls@v0.14.2](https://github.com/golang/tools/releases/tag/gopls%2Fv0.14.2) |
|
||||
| Go 1.20 | [gopls@v0.15.3](https://github.com/golang/tools/releases/tag/gopls%2Fv0.15.3) |
|
||||
|
||||
### Supported build systems
|
||||
|
||||
`gopls` currently only supports the `go` command, so if you are using
|
||||
a different build system, `gopls` will not work well. Bazel is not officially
|
||||
supported, but may be made to work with an appropriately configured
|
||||
`go/packages` driver. See
|
||||
[bazelbuild/rules_go#512](https://github.com/bazelbuild/rules_go/issues/512)
|
||||
for more information.
|
||||
You can follow [these instructions](https://github.com/bazelbuild/rules_go/wiki/Editor-setup)
|
||||
to configure your `gopls` to work with Bazel.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
If you are having issues with `gopls`, please follow the steps described in the
|
||||
[troubleshooting guide](doc/troubleshooting.md).
|
||||
|
||||
## Additional information
|
||||
|
||||
* [Features](doc/features.md)
|
||||
* [Command-line interface](doc/command-line.md)
|
||||
* [Advanced topics](doc/advanced.md)
|
||||
* [Contributing to `gopls`](doc/contributing.md)
|
||||
* [Integrating `gopls` with an editor](doc/design/integrating.md)
|
||||
* [Design requirements and decisions](doc/design/design.md)
|
||||
* [Implementation details](doc/design/implementation.md)
|
||||
* [Open issues](https://github.com/golang/go/issues?q=is%3Aissue+is%3Aopen+label%3Agopls)
|
||||
|
||||
[language server]: https://langserver.org
|
||||
[LSP]: https://microsoft.github.io/language-server-protocol/
|
||||
@@ -0,0 +1,80 @@
|
||||
# Advanced topics
|
||||
|
||||
This documentation is for advanced `gopls` users, who may want to test
|
||||
unreleased versions or try out special features.
|
||||
|
||||
## Installing unreleased versions
|
||||
|
||||
To get a specific version of `gopls` (for example, to test a prerelease
|
||||
version), run:
|
||||
|
||||
```sh
|
||||
GO111MODULE=on go install golang.org/x/tools/gopls@vX.Y.Z
|
||||
```
|
||||
|
||||
Where `vX.Y.Z` is the desired version.
|
||||
|
||||
### Unstable versions
|
||||
|
||||
To update `gopls` to the latest **unstable** version, use the following
|
||||
commands.
|
||||
|
||||
```sh
|
||||
# Create an empty go.mod file, only for tracking requirements.
|
||||
cd $(mktemp -d)
|
||||
go mod init gopls-unstable
|
||||
|
||||
# Use 'go get' to add requirements and to ensure they work together.
|
||||
go get -d golang.org/x/tools/gopls@master golang.org/x/tools@master
|
||||
|
||||
go install golang.org/x/tools/gopls
|
||||
```
|
||||
|
||||
## Working on the Go source distribution
|
||||
|
||||
If you are working on the [Go project] itself, the `go` command that `gopls`
|
||||
invokes will have to correspond to the version of the source you are working
|
||||
on. That is, if you have checked out the Go project to `$HOME/go`, your `go`
|
||||
command should be the `$HOME/go/bin/go` executable that you built with
|
||||
`make.bash` or equivalent.
|
||||
|
||||
You can achieve this by adding the right version of `go` to your `PATH`
|
||||
(`export PATH=$HOME/go/bin:$PATH` on Unix systems) or by configuring your
|
||||
editor.
|
||||
|
||||
To work on both `std` and `cmd` simultaneously, add a `go.work` file to
|
||||
`GOROOT/src`:
|
||||
|
||||
```
|
||||
cd $(go env GOROOT)/src
|
||||
go work init . cmd
|
||||
```
|
||||
|
||||
Note that you must work inside the `GOROOT/src` subdirectory, as the `go`
|
||||
command does not recognize `go.work` files in a parent of `GOROOT/src`
|
||||
(https://go.dev/issue/59429).
|
||||
|
||||
## Working with generic code
|
||||
|
||||
Gopls has support for editing generic Go code. To enable this support, you need
|
||||
to **install gopls using Go 1.18 or later**. The easiest way to do this is by
|
||||
[installing Go 1.18+](https://go.dev/dl) and then using this Go version to
|
||||
install gopls:
|
||||
|
||||
```
|
||||
$ go install golang.org/x/tools/gopls@latest
|
||||
```
|
||||
|
||||
It is strongly recommended that you install the latest version of `gopls`, or
|
||||
the latest **unstable** version as [described above](#installing-unreleased-versions).
|
||||
|
||||
The `gopls` built with these instructions understands generic code. See the
|
||||
[generics tutorial](https://go.dev/doc/tutorial/generics) for more information
|
||||
on how to use generics in Go!
|
||||
|
||||
### Known issues
|
||||
|
||||
* [`staticcheck`](https://github.com/golang/tools/blob/master/gopls/doc/settings.md#staticcheck)
|
||||
on generic code is not supported yet.
|
||||
|
||||
[Go project]: https://go.googlesource.com/go
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,153 @@
|
||||
# Code Lenses
|
||||
|
||||
A "code lens" is a command associated with a range of a source file.
|
||||
The VS Code manual describes code lenses as
|
||||
"[actionable, contextual information, interspersed in your source
|
||||
code](https://code.visualstudio.com/blogs/2017/02/12/code-lens-roundup)".
|
||||
The LSP [`textDocument/codeLens`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_codeLens) operation requests the
|
||||
current set of code lenses for a file.
|
||||
|
||||
Gopls generates code lenses from a number of sources.
|
||||
This document describes them.
|
||||
|
||||
They can be enabled and disabled using the
|
||||
[`codelenses`](settings.md#codelenses) setting.
|
||||
Their features are subject to change.
|
||||
|
||||
<!-- This portion is generated by doc/generate from the ../internal/settings package. -->
|
||||
<!-- BEGIN Lenses: DO NOT MANUALLY EDIT THIS SECTION -->
|
||||
## `gc_details`: Toggle display of Go compiler optimization decisions
|
||||
|
||||
|
||||
This codelens source causes the `package` declaration of
|
||||
each file to be annotated with a command to toggle the
|
||||
state of the per-session variable that controls whether
|
||||
optimization decisions from the Go compiler (formerly known
|
||||
as "gc") should be displayed as diagnostics.
|
||||
|
||||
Optimization decisions include:
|
||||
- whether a variable escapes, and how escape is inferred;
|
||||
- whether a nil-pointer check is implied or eliminated;
|
||||
- whether a function can be inlined.
|
||||
|
||||
TODO(adonovan): this source is off by default because the
|
||||
annotation is annoying and because VS Code has a separate
|
||||
"Toggle gc details" command. Replace it with a Code Action
|
||||
("Source action...").
|
||||
|
||||
|
||||
Default: off
|
||||
|
||||
File type: Go
|
||||
|
||||
## `generate`: Run `go generate`
|
||||
|
||||
|
||||
This codelens source annotates any `//go:generate` comments
|
||||
with commands to run `go generate` in this directory, on
|
||||
all directories recursively beneath this one.
|
||||
|
||||
See [Generating code](https://go.dev/blog/generate) for
|
||||
more details.
|
||||
|
||||
|
||||
Default: on
|
||||
|
||||
File type: Go
|
||||
|
||||
## `regenerate_cgo`: Re-generate cgo declarations
|
||||
|
||||
|
||||
This codelens source annotates an `import "C"` declaration
|
||||
with a command to re-run the [cgo
|
||||
command](https://pkg.go.dev/cmd/cgo) to regenerate the
|
||||
corresponding Go declarations.
|
||||
|
||||
Use this after editing the C code in comments attached to
|
||||
the import, or in C header files included by it.
|
||||
|
||||
|
||||
Default: on
|
||||
|
||||
File type: Go
|
||||
|
||||
## `test`: Run tests and benchmarks
|
||||
|
||||
|
||||
This codelens source annotates each `Test` and `Benchmark`
|
||||
function in a `*_test.go` file with a command to run it.
|
||||
|
||||
This source is off by default because VS Code has
|
||||
a client-side custom UI for testing, and because progress
|
||||
notifications are not a great UX for streamed test output.
|
||||
See:
|
||||
- golang/go#67400 for a discussion of this feature.
|
||||
- https://github.com/joaotavora/eglot/discussions/1402
|
||||
for an alternative approach.
|
||||
|
||||
|
||||
Default: off
|
||||
|
||||
File type: Go
|
||||
|
||||
## `run_govulncheck`: Run govulncheck
|
||||
|
||||
|
||||
This codelens source annotates the `module` directive in a
|
||||
go.mod file with a command to run Govulncheck.
|
||||
|
||||
[Govulncheck](https://go.dev/blog/vuln) is a static
|
||||
analysis tool that computes the set of functions reachable
|
||||
within your application, including dependencies;
|
||||
queries a database of known security vulnerabilities; and
|
||||
reports any potential problems it finds.
|
||||
|
||||
|
||||
Default: off
|
||||
|
||||
File type: go.mod
|
||||
|
||||
## `tidy`: Tidy go.mod file
|
||||
|
||||
|
||||
This codelens source annotates the `module` directive in a
|
||||
go.mod file with a command to run [`go mod
|
||||
tidy`](https://go.dev/ref/mod#go-mod-tidy), which ensures
|
||||
that the go.mod file matches the source code in the module.
|
||||
|
||||
|
||||
Default: on
|
||||
|
||||
File type: go.mod
|
||||
|
||||
## `upgrade_dependency`: Update dependencies
|
||||
|
||||
|
||||
This codelens source annotates the `module` directive in a
|
||||
go.mod file with commands to:
|
||||
|
||||
- check for available upgrades,
|
||||
- upgrade direct dependencies, and
|
||||
- upgrade all dependencies transitively.
|
||||
|
||||
|
||||
Default: on
|
||||
|
||||
File type: go.mod
|
||||
|
||||
## `vendor`: Update vendor directory
|
||||
|
||||
|
||||
This codelens source annotates the `module` directive in a
|
||||
go.mod file with a command to run [`go mod
|
||||
vendor`](https://go.dev/ref/mod#go-mod-vendor), which
|
||||
creates or updates the directory named `vendor` in the
|
||||
module root so that it contains an up-to-date copy of all
|
||||
necessary package dependencies.
|
||||
|
||||
|
||||
Default: on
|
||||
|
||||
File type: go.mod
|
||||
|
||||
<!-- END Lenses: DO NOT MANUALLY EDIT THIS SECTION -->
|
||||
@@ -0,0 +1,15 @@
|
||||
# Command line
|
||||
|
||||
**Note: The `gopls` command-line is still experimental and subject to change at any point.**
|
||||
|
||||
`gopls` exposes some (but not all) features on the command-line. This can be useful for debugging `gopls` itself.
|
||||
|
||||
<!--TODO(rstambler): Generate this file.-->
|
||||
|
||||
Learn about available commands and flags by running `gopls help`.
|
||||
|
||||
Much of the functionality of `gopls` is available through a command line interface.
|
||||
|
||||
There are two main reasons for this. The first is that we do not want users to rely on separate command line tools when they wish to do some task outside of an editor. The second is that the CLI assists in debugging. It is easier to reproduce behavior via single command.
|
||||
|
||||
It is not a goal of `gopls` to be a high performance command line tool. Its command line is intended for single file/package user interaction speeds, not bulk processing.
|
||||
@@ -0,0 +1,798 @@
|
||||
# Commands
|
||||
|
||||
This document describes the LSP-level commands supported by `gopls`. They cannot be invoked directly by users, and all the details are subject to change, so nobody should rely on this information.
|
||||
|
||||
<!-- BEGIN Commands: DO NOT MANUALLY EDIT THIS SECTION -->
|
||||
## `gopls.add_dependency`: **Add a dependency**
|
||||
|
||||
Adds a dependency to the go.mod file for a module.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
{
|
||||
// The go.mod file URI.
|
||||
"URI": string,
|
||||
// Additional args to pass to the go command.
|
||||
"GoCmdArgs": []string,
|
||||
// Whether to add a require directive.
|
||||
"AddRequire": bool,
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.add_import`: **Add an import**
|
||||
|
||||
Ask the server to add an import path to a given Go file. The method will
|
||||
call applyEdit on the client so that clients don't have to apply the edit
|
||||
themselves.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
{
|
||||
// ImportPath is the target import path that should
|
||||
// be added to the URI file
|
||||
"ImportPath": string,
|
||||
// URI is the file that the ImportPath should be
|
||||
// added to
|
||||
"URI": string,
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.add_telemetry_counters`: **Update the given telemetry counters**
|
||||
|
||||
Gopls will prepend "fwd/" to all the counters updated using this command
|
||||
to avoid conflicts with other counters gopls collects.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
{
|
||||
// Names and Values must have the same length.
|
||||
"Names": []string,
|
||||
"Values": []int64,
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.apply_fix`: **Apply a fix**
|
||||
|
||||
Applies a fix to a region of source code.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
{
|
||||
// The name of the fix to apply.
|
||||
//
|
||||
// For fixes suggested by analyzers, this is a string constant
|
||||
// advertised by the analyzer that matches the Category of
|
||||
// the analysis.Diagnostic with a SuggestedFix containing no edits.
|
||||
//
|
||||
// For fixes suggested by code actions, this is a string agreed
|
||||
// upon by the code action and golang.ApplyFix.
|
||||
"Fix": string,
|
||||
// The file URI for the document to fix.
|
||||
"URI": string,
|
||||
// The document range to scan for fixes.
|
||||
"Range": {
|
||||
"start": {
|
||||
"line": uint32,
|
||||
"character": uint32,
|
||||
},
|
||||
"end": {
|
||||
"line": uint32,
|
||||
"character": uint32,
|
||||
},
|
||||
},
|
||||
// Whether to resolve and return the edits.
|
||||
"ResolveEdits": bool,
|
||||
}
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```
|
||||
{
|
||||
// Holds changes to existing resources.
|
||||
"changes": map[golang.org/x/tools/gopls/internal/protocol.DocumentURI][]golang.org/x/tools/gopls/internal/protocol.TextEdit,
|
||||
// Depending on the client capability `workspace.workspaceEdit.resourceOperations` document changes
|
||||
// are either an array of `TextDocumentEdit`s to express changes to n different text documents
|
||||
// where each text document edit addresses a specific version of a text document. Or it can contain
|
||||
// above `TextDocumentEdit`s mixed with create, rename and delete file / folder operations.
|
||||
//
|
||||
// Whether a client supports versioned document edits is expressed via
|
||||
// `workspace.workspaceEdit.documentChanges` client capability.
|
||||
//
|
||||
// If a client neither supports `documentChanges` nor `workspace.workspaceEdit.resourceOperations` then
|
||||
// only plain `TextEdit`s using the `changes` property are supported.
|
||||
"documentChanges": []{
|
||||
"TextDocumentEdit": {
|
||||
"textDocument": { ... },
|
||||
"edits": { ... },
|
||||
},
|
||||
"CreateFile": {
|
||||
"kind": string,
|
||||
"uri": string,
|
||||
"options": { ... },
|
||||
"ResourceOperation": { ... },
|
||||
},
|
||||
"RenameFile": {
|
||||
"kind": string,
|
||||
"oldUri": string,
|
||||
"newUri": string,
|
||||
"options": { ... },
|
||||
"ResourceOperation": { ... },
|
||||
},
|
||||
"DeleteFile": {
|
||||
"kind": string,
|
||||
"uri": string,
|
||||
"options": { ... },
|
||||
"ResourceOperation": { ... },
|
||||
},
|
||||
},
|
||||
// A map of change annotations that can be referenced in `AnnotatedTextEdit`s or create, rename and
|
||||
// delete file / folder operations.
|
||||
//
|
||||
// Whether clients honor this property depends on the client capability `workspace.changeAnnotationSupport`.
|
||||
//
|
||||
// @since 3.16.0
|
||||
"changeAnnotations": map[string]golang.org/x/tools/gopls/internal/protocol.ChangeAnnotation,
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.assembly`: **Browse assembly listing of current function in a browser.**
|
||||
|
||||
This command opens a web-based disassembly listing of the
|
||||
specified function symbol (plus any nested lambdas and defers).
|
||||
The machine architecture is determined by the view.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
string,
|
||||
string,
|
||||
string
|
||||
```
|
||||
|
||||
## `gopls.change_signature`: **Perform a "change signature" refactoring**
|
||||
|
||||
This command is experimental, currently only supporting parameter removal.
|
||||
Its signature will certainly change in the future (pun intended).
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
{
|
||||
"RemoveParameter": {
|
||||
"uri": string,
|
||||
"range": {
|
||||
"start": { ... },
|
||||
"end": { ... },
|
||||
},
|
||||
},
|
||||
// Whether to resolve and return the edits.
|
||||
"ResolveEdits": bool,
|
||||
}
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```
|
||||
{
|
||||
// Holds changes to existing resources.
|
||||
"changes": map[golang.org/x/tools/gopls/internal/protocol.DocumentURI][]golang.org/x/tools/gopls/internal/protocol.TextEdit,
|
||||
// Depending on the client capability `workspace.workspaceEdit.resourceOperations` document changes
|
||||
// are either an array of `TextDocumentEdit`s to express changes to n different text documents
|
||||
// where each text document edit addresses a specific version of a text document. Or it can contain
|
||||
// above `TextDocumentEdit`s mixed with create, rename and delete file / folder operations.
|
||||
//
|
||||
// Whether a client supports versioned document edits is expressed via
|
||||
// `workspace.workspaceEdit.documentChanges` client capability.
|
||||
//
|
||||
// If a client neither supports `documentChanges` nor `workspace.workspaceEdit.resourceOperations` then
|
||||
// only plain `TextEdit`s using the `changes` property are supported.
|
||||
"documentChanges": []{
|
||||
"TextDocumentEdit": {
|
||||
"textDocument": { ... },
|
||||
"edits": { ... },
|
||||
},
|
||||
"CreateFile": {
|
||||
"kind": string,
|
||||
"uri": string,
|
||||
"options": { ... },
|
||||
"ResourceOperation": { ... },
|
||||
},
|
||||
"RenameFile": {
|
||||
"kind": string,
|
||||
"oldUri": string,
|
||||
"newUri": string,
|
||||
"options": { ... },
|
||||
"ResourceOperation": { ... },
|
||||
},
|
||||
"DeleteFile": {
|
||||
"kind": string,
|
||||
"uri": string,
|
||||
"options": { ... },
|
||||
"ResourceOperation": { ... },
|
||||
},
|
||||
},
|
||||
// A map of change annotations that can be referenced in `AnnotatedTextEdit`s or create, rename and
|
||||
// delete file / folder operations.
|
||||
//
|
||||
// Whether clients honor this property depends on the client capability `workspace.changeAnnotationSupport`.
|
||||
//
|
||||
// @since 3.16.0
|
||||
"changeAnnotations": map[string]golang.org/x/tools/gopls/internal/protocol.ChangeAnnotation,
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.check_upgrades`: **Check for upgrades**
|
||||
|
||||
Checks for module upgrades.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
{
|
||||
// The go.mod file URI.
|
||||
"URI": string,
|
||||
// The modules to check.
|
||||
"Modules": []string,
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.diagnose_files`: **Cause server to publish diagnostics for the specified files.**
|
||||
|
||||
This command is needed by the 'gopls {check,fix}' CLI subcommands.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
{
|
||||
"Files": []string,
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.doc`: **Browse package documentation.**
|
||||
|
||||
Opens the Go package documentation page for the current
|
||||
package in a browser.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
{
|
||||
"uri": string,
|
||||
"range": {
|
||||
"start": {
|
||||
"line": uint32,
|
||||
"character": uint32,
|
||||
},
|
||||
"end": {
|
||||
"line": uint32,
|
||||
"character": uint32,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.edit_go_directive`: **Run go mod edit -go=version**
|
||||
|
||||
Runs `go mod edit -go=version` for a module.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
{
|
||||
// Any document URI within the relevant module.
|
||||
"URI": string,
|
||||
// The version to pass to `go mod edit -go`.
|
||||
"Version": string,
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.fetch_vulncheck_result`: **Get known vulncheck result**
|
||||
|
||||
Fetch the result of latest vulnerability check (`govulncheck`).
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
{
|
||||
// The file URI.
|
||||
"URI": string,
|
||||
}
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```
|
||||
map[golang.org/x/tools/gopls/internal/protocol.DocumentURI]*golang.org/x/tools/gopls/internal/vulncheck.Result
|
||||
```
|
||||
|
||||
## `gopls.free_symbols`: **Browse free symbols referenced by the selection in a browser.**
|
||||
|
||||
This command is a query over a selected range of Go source
|
||||
code. It reports the set of "free" symbols of the
|
||||
selection: the set of symbols that are referenced within
|
||||
the selection but are declared outside of it. This
|
||||
information is useful for understanding at a glance what a
|
||||
block of code depends on, perhaps as a precursor to
|
||||
extracting it into a separate function.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
string,
|
||||
{
|
||||
"uri": string,
|
||||
"range": {
|
||||
"start": {
|
||||
"line": uint32,
|
||||
"character": uint32,
|
||||
},
|
||||
"end": {
|
||||
"line": uint32,
|
||||
"character": uint32,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.gc_details`: **Toggle gc_details**
|
||||
|
||||
Toggle the calculation of gc annotations.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
string
|
||||
```
|
||||
|
||||
## `gopls.generate`: **Run go generate**
|
||||
|
||||
Runs `go generate` for a given directory.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
{
|
||||
// URI for the directory to generate.
|
||||
"Dir": string,
|
||||
// Whether to generate recursively (go generate ./...)
|
||||
"Recursive": bool,
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.go_get_package`: **'go get' a package**
|
||||
|
||||
Runs `go get` to fetch a package.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
{
|
||||
// Any document URI within the relevant module.
|
||||
"URI": string,
|
||||
// The package to go get.
|
||||
"Pkg": string,
|
||||
"AddRequire": bool,
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.list_imports`: **List imports of a file and its package**
|
||||
|
||||
Retrieve a list of imports in the given Go file, and the package it
|
||||
belongs to.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
{
|
||||
// The file URI.
|
||||
"URI": string,
|
||||
}
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```
|
||||
{
|
||||
// Imports is a list of imports in the requested file.
|
||||
"Imports": []{
|
||||
"Path": string,
|
||||
"Name": string,
|
||||
},
|
||||
// PackageImports is a list of all imports in the requested file's package.
|
||||
"PackageImports": []{
|
||||
"Path": string,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.list_known_packages`: **List known packages**
|
||||
|
||||
Retrieve a list of packages that are importable from the given URI.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
{
|
||||
// The file URI.
|
||||
"URI": string,
|
||||
}
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```
|
||||
{
|
||||
// Packages is a list of packages relative
|
||||
// to the URIArg passed by the command request.
|
||||
// In other words, it omits paths that are already
|
||||
// imported or cannot be imported due to compiler
|
||||
// restrictions.
|
||||
"Packages": []string,
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.maybe_prompt_for_telemetry`: **Prompt user to enable telemetry**
|
||||
|
||||
Checks for the right conditions, and then prompts the user
|
||||
to ask if they want to enable Go telemetry uploading. If
|
||||
the user responds 'Yes', the telemetry mode is set to "on".
|
||||
|
||||
## `gopls.mem_stats`: **Fetch memory statistics**
|
||||
|
||||
Call runtime.GC multiple times and return memory statistics as reported by
|
||||
runtime.MemStats.
|
||||
|
||||
This command is used for benchmarking, and may change in the future.
|
||||
|
||||
Result:
|
||||
|
||||
```
|
||||
{
|
||||
"HeapAlloc": uint64,
|
||||
"HeapInUse": uint64,
|
||||
"TotalAlloc": uint64,
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.regenerate_cgo`: **Regenerate cgo**
|
||||
|
||||
Regenerates cgo definitions.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
{
|
||||
// The file URI.
|
||||
"URI": string,
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.remove_dependency`: **Remove a dependency**
|
||||
|
||||
Removes a dependency from the go.mod file of a module.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
{
|
||||
// The go.mod file URI.
|
||||
"URI": string,
|
||||
// The module path to remove.
|
||||
"ModulePath": string,
|
||||
// If the module is tidied apart from the one unused diagnostic, we can
|
||||
// run `go get module@none`, and then run `go mod tidy`. Otherwise, we
|
||||
// must make textual edits.
|
||||
"OnlyDiagnostic": bool,
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.reset_go_mod_diagnostics`: **Reset go.mod diagnostics**
|
||||
|
||||
Reset diagnostics in the go.mod file of a module.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
{
|
||||
"URIArg": {
|
||||
"URI": string,
|
||||
},
|
||||
// Optional: source of the diagnostics to reset.
|
||||
// If not set, all resettable go.mod diagnostics will be cleared.
|
||||
"DiagnosticSource": string,
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.run_go_work_command`: **Run `go work [args...]`, and apply the resulting go.work**
|
||||
|
||||
edits to the current go.work file
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
{
|
||||
"ViewID": string,
|
||||
"InitFirst": bool,
|
||||
"Args": []string,
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.run_govulncheck`: **Run vulncheck**
|
||||
|
||||
Run vulnerability check (`govulncheck`).
|
||||
|
||||
This command is asynchronous; clients must wait for the 'end' progress notification.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
{
|
||||
// Any document in the directory from which govulncheck will run.
|
||||
"URI": string,
|
||||
// Package pattern. E.g. "", ".", "./...".
|
||||
"Pattern": string,
|
||||
}
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```
|
||||
{
|
||||
// Token holds the progress token for LSP workDone reporting of the vulncheck
|
||||
// invocation.
|
||||
"Token": interface{},
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.run_tests`: **Run test(s)**
|
||||
|
||||
Runs `go test` for a specific set of test or benchmark functions.
|
||||
|
||||
This command is asynchronous; clients must wait for the 'end' progress notification.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
{
|
||||
// The test file containing the tests to run.
|
||||
"URI": string,
|
||||
// Specific test names to run, e.g. TestFoo.
|
||||
"Tests": []string,
|
||||
// Specific benchmarks to run, e.g. BenchmarkFoo.
|
||||
"Benchmarks": []string,
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.scan_imports`: **force a sychronous scan of the imports cache.**
|
||||
|
||||
This command is intended for use by gopls tests only.
|
||||
|
||||
## `gopls.start_debugging`: **Start the gopls debug server**
|
||||
|
||||
Start the gopls debug server if it isn't running, and return the debug
|
||||
address.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
{
|
||||
// Optional: the address (including port) for the debug server to listen on.
|
||||
// If not provided, the debug server will bind to "localhost:0", and the
|
||||
// full debug URL will be contained in the result.
|
||||
//
|
||||
// If there is more than one gopls instance along the serving path (i.e. you
|
||||
// are using a daemon), each gopls instance will attempt to start debugging.
|
||||
// If Addr specifies a port, only the daemon will be able to bind to that
|
||||
// port, and each intermediate gopls instance will fail to start debugging.
|
||||
// For this reason it is recommended not to specify a port (or equivalently,
|
||||
// to specify ":0").
|
||||
//
|
||||
// If the server was already debugging this field has no effect, and the
|
||||
// result will contain the previously configured debug URL(s).
|
||||
"Addr": string,
|
||||
}
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```
|
||||
{
|
||||
// The URLs to use to access the debug servers, for all gopls instances in
|
||||
// the serving path. For the common case of a single gopls instance (i.e. no
|
||||
// daemon), this will be exactly one address.
|
||||
//
|
||||
// In the case of one or more gopls instances forwarding the LSP to a daemon,
|
||||
// URLs will contain debug addresses for each server in the serving path, in
|
||||
// serving order. The daemon debug address will be the last entry in the
|
||||
// slice. If any intermediate gopls instance fails to start debugging, no
|
||||
// error will be returned but the debug URL for that server in the URLs slice
|
||||
// will be empty.
|
||||
"URLs": []string,
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.start_profile`: **Start capturing a profile of gopls' execution**
|
||||
|
||||
Start a new pprof profile. Before using the resulting file, profiling must
|
||||
be stopped with a corresponding call to StopProfile.
|
||||
|
||||
This command is intended for internal use only, by the gopls benchmark
|
||||
runner.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
struct{}
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```
|
||||
struct{}
|
||||
```
|
||||
|
||||
## `gopls.stop_profile`: **Stop an ongoing profile**
|
||||
|
||||
This command is intended for internal use only, by the gopls benchmark
|
||||
runner.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
struct{}
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```
|
||||
{
|
||||
// File is the profile file name.
|
||||
"File": string,
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.test`: **Run test(s) (legacy)**
|
||||
|
||||
Runs `go test` for a specific set of test or benchmark functions.
|
||||
|
||||
This command is asynchronous; wait for the 'end' progress notification.
|
||||
|
||||
This command is an alias for RunTests; the only difference
|
||||
is the form of the parameters.
|
||||
|
||||
TODO(adonovan): eliminate it.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
string,
|
||||
[]string,
|
||||
[]string
|
||||
```
|
||||
|
||||
## `gopls.tidy`: **Run go mod tidy**
|
||||
|
||||
Runs `go mod tidy` for a module.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
{
|
||||
// The file URIs.
|
||||
"URIs": []string,
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.toggle_gc_details`: **Toggle gc_details**
|
||||
|
||||
Toggle the calculation of gc annotations.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
{
|
||||
// The file URI.
|
||||
"URI": string,
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.update_go_sum`: **Update go.sum**
|
||||
|
||||
Updates the go.sum file for a module.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
{
|
||||
// The file URIs.
|
||||
"URIs": []string,
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.upgrade_dependency`: **Upgrade a dependency**
|
||||
|
||||
Upgrades a dependency in the go.mod file for a module.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
{
|
||||
// The go.mod file URI.
|
||||
"URI": string,
|
||||
// Additional args to pass to the go command.
|
||||
"GoCmdArgs": []string,
|
||||
// Whether to add a require directive.
|
||||
"AddRequire": bool,
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.vendor`: **Run go mod vendor**
|
||||
|
||||
Runs `go mod vendor` for a module.
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
{
|
||||
// The file URI.
|
||||
"URI": string,
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.views`: **List current Views on the server.**
|
||||
|
||||
This command is intended for use by gopls tests only.
|
||||
|
||||
Result:
|
||||
|
||||
```
|
||||
[]{
|
||||
"ID": string,
|
||||
"Type": string,
|
||||
"Root": string,
|
||||
"Folder": string,
|
||||
"EnvOverlay": []string,
|
||||
}
|
||||
```
|
||||
|
||||
## `gopls.workspace_stats`: **Fetch workspace statistics**
|
||||
|
||||
Query statistics about workspace builds, modules, packages, and files.
|
||||
|
||||
This command is intended for internal use only, by the gopls stats
|
||||
command.
|
||||
|
||||
Result:
|
||||
|
||||
```
|
||||
{
|
||||
"Files": {
|
||||
"Total": int,
|
||||
"Largest": int,
|
||||
"Errs": int,
|
||||
},
|
||||
"Views": []{
|
||||
"GoCommandVersion": string,
|
||||
"AllPackages": {
|
||||
"Packages": int,
|
||||
"LargestPackage": int,
|
||||
"CompiledGoFiles": int,
|
||||
"Modules": int,
|
||||
},
|
||||
"WorkspacePackages": {
|
||||
"Packages": int,
|
||||
"LargestPackage": int,
|
||||
"CompiledGoFiles": int,
|
||||
"Modules": int,
|
||||
},
|
||||
"Diagnostics": int,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
<!-- END Commands: DO NOT MANUALLY EDIT THIS SECTION -->
|
||||
@@ -0,0 +1,167 @@
|
||||
# Documentation for contributors
|
||||
|
||||
This documentation augments the general documentation for contributing to the
|
||||
x/tools repository, described at the [repository root](../../CONTRIBUTING.md).
|
||||
|
||||
Contributions are welcome, but since development is so active, we request that
|
||||
you file an issue and claim it before starting to work on something. Otherwise,
|
||||
it is likely that we might already be working on a fix for your issue.
|
||||
|
||||
## Finding issues
|
||||
|
||||
All `gopls` issues are labeled as such (see the [`gopls` label][issue-gopls]).
|
||||
Issues that are suitable for contributors are additionally tagged with the
|
||||
[`help-wanted` label][issue-wanted].
|
||||
|
||||
Before you begin working on an issue, please leave a comment that you are
|
||||
claiming it.
|
||||
|
||||
## Getting started
|
||||
|
||||
Most of the `gopls` logic is in the `golang.org/x/tools/gopls/internal`
|
||||
directory. See [design/implementation.md] for an overview of the code organization.
|
||||
|
||||
## Build
|
||||
|
||||
To build a version of `gopls` with your changes applied:
|
||||
|
||||
```bash
|
||||
cd /path/to/tools/gopls
|
||||
go install
|
||||
```
|
||||
|
||||
To confirm that you are testing with the correct `gopls` version, check that
|
||||
your `gopls` version looks like this:
|
||||
|
||||
```bash
|
||||
$ gopls version
|
||||
golang.org/x/tools/gopls master
|
||||
golang.org/x/tools/gopls@(devel)
|
||||
```
|
||||
|
||||
## Getting help
|
||||
|
||||
The best way to contact the gopls team directly is via the
|
||||
[#gopls-dev](https://app.slack.com/client/T029RQSE6/CRWSN9NCD) channel on the
|
||||
gophers slack. Please feel free to ask any questions about your contribution or
|
||||
about contributing in general.
|
||||
|
||||
|
||||
## Error handling
|
||||
|
||||
It is important for the user experience that, whenever practical,
|
||||
minor logic errors in a particular feature don't cause the server to
|
||||
crash.
|
||||
|
||||
The representation of a Go program is complex. The import graph of
|
||||
package metadata, the syntax trees of parsed files, and their
|
||||
associated type information together form a huge API surface area.
|
||||
Even when the input is valid, there are many edge cases to consider,
|
||||
and this grows by an order of magnitude when you consider missing
|
||||
imports, parse errors, and type errors.
|
||||
|
||||
What should you do when your logic must handle an error that you
|
||||
believe "can't happen"?
|
||||
|
||||
- If it's possible to return an error, then use the `bug.Errorf`
|
||||
function to return an error to the user, but also record the bug in
|
||||
gopls' cache so that it is less likely to be ignored.
|
||||
|
||||
- If it's safe to proceed, you can call `bug.Reportf` to record the
|
||||
error and continue as normal.
|
||||
|
||||
- If there's no way to proceed, call `bug.Fatalf` to record the error
|
||||
and then stop the program with `log.Fatalf`. You can also use
|
||||
`bug.Panicf` if there's a chance that a recover handler might save
|
||||
the situation.
|
||||
|
||||
- Only if you can prove locally that an error is impossible should you
|
||||
call `log.Fatal`. If the error may happen for some input, however
|
||||
unlikely, then you should use one of the approaches above. Also, if
|
||||
the proof of safety depends on invariants broadly distributed across
|
||||
the code base, then you should instead use `bug.Panicf`.
|
||||
|
||||
Note also that panicking is preferable to `log.Fatal` because it
|
||||
allows VS Code's crash reporting to recognize and capture the stack.
|
||||
|
||||
Bugs reported through `bug.Errorf` and friends are retrieved using the
|
||||
`gopls bug` command, which opens a GitHub Issue template and populates
|
||||
it with a summary of each bug and its frequency.
|
||||
The text of the bug is rather fastidiously printed to stdout to avoid
|
||||
sharing user names and error message strings (which could contain
|
||||
project identifiers) with GitHub.
|
||||
Users are invited to share it if they are willing.
|
||||
|
||||
## Testing
|
||||
|
||||
The normal command you should use to run the tests after a change is:
|
||||
|
||||
```bash
|
||||
gopls$ go test -short ./...
|
||||
```
|
||||
|
||||
(The `-short` flag skips some slow-running ones. The trybot builders
|
||||
run the complete set, on a wide range of platforms.)
|
||||
|
||||
Gopls tests are a mix of two kinds.
|
||||
|
||||
- [Marker tests](../internal/test/marker) express each test scenario
|
||||
in a standalone text file that contains the target .go, go.mod, and
|
||||
go.work files, in which special annotations embedded in comments
|
||||
drive the test. These tests are generally easy to write and fast
|
||||
to iterate, but have limitations on what they can express.
|
||||
|
||||
- [Integration tests](../internal/test/integration) are regular Go
|
||||
`func Test(*testing.T)` functions that make a series of calls to an
|
||||
API for a fake LSP-enabled client editor. The API allows you to open
|
||||
and edit a file, navigate to a definition, invoke other LSP
|
||||
operations, and assert properties about the state.
|
||||
|
||||
Due to the asynchronous nature of the LSP, integration tests make
|
||||
assertions about states that the editor must achieve eventually,
|
||||
even when the program goes wrong quickly, it may take a while before
|
||||
the error is reported as a failure to achieve the desired state
|
||||
within several minutes. We recommend that you set
|
||||
`GOPLS_INTEGRATION_TEST_TIMEOUT=10s` to reduce the timeout for
|
||||
integration tests when debugging.
|
||||
|
||||
When they fail, the integration tests print the log of the LSP
|
||||
session between client and server. Though verbose, they are very
|
||||
helpful for debugging once you know how to read them.
|
||||
|
||||
Don't hesitate to [reach out](#getting-help) to the gopls team if you
|
||||
need help.
|
||||
|
||||
### CI
|
||||
|
||||
When you mail your CL and you or a fellow contributor assigns the
|
||||
`Run-TryBot=1` label in Gerrit, the
|
||||
[TryBots](https://golang.org/doc/contribute.html#trybots) will run tests in
|
||||
both the `golang.org/x/tools` and `golang.org/x/tools/gopls` modules, as
|
||||
described above.
|
||||
|
||||
Furthermore, an additional "gopls-CI" pass will be run by _Kokoro_, which is a
|
||||
Jenkins-like Google infrastructure for running Dockerized tests. This allows us
|
||||
to run gopls tests in various environments that would be difficult to add to
|
||||
the TryBots. Notably, Kokoro runs tests on
|
||||
[older Go versions](../README.md#supported-go-versions) that are no longer supported
|
||||
by the TryBots. Per that that policy, support for these older Go versions is
|
||||
best-effort, and test failures may be skipped rather than fixed.
|
||||
|
||||
Kokoro runs are triggered by the `Run-TryBot=1` label, just like TryBots, but
|
||||
unlike TryBots they do not automatically re-run if the "gopls-CI" result is
|
||||
removed in Gerrit. To force a re-run of the Kokoro CI on a CL containing the
|
||||
`Run-TryBot=1` label, you can reply in Gerrit with the comment "kokoro rerun".
|
||||
|
||||
## Debugging
|
||||
|
||||
The easiest way to debug your change is to run a single `gopls` test with a
|
||||
debugger.
|
||||
|
||||
See also [Troubleshooting](troubleshooting.md#troubleshooting).
|
||||
|
||||
<!--TODO(rstambler): Add more details about the debug server and viewing
|
||||
telemetry.-->
|
||||
|
||||
[issue-gopls]: https://github.com/golang/go/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3Agopls "gopls issues"
|
||||
[issue-wanted]: https://github.com/golang/go/issues?utf8=✓&q=is%3Aissue+is%3Aopen+label%3Agopls+label%3A"help+wanted" "help wanted"
|
||||
@@ -0,0 +1,183 @@
|
||||
# Running gopls as a daemon
|
||||
|
||||
**Note: this feature is new. If you encounter bugs, please [file an
|
||||
issue](troubleshooting.md#file-an-issue).**
|
||||
|
||||
If you just want to try this out, skip ahead to the [quickstart](#quickstart).
|
||||
|
||||
## Background: gopls execution modes
|
||||
|
||||
Gopls was originally implemented as an LSP sidecar: a process started by
|
||||
editors or editor plugins, and communicated with using jsonrpc 2.0 over
|
||||
stdin/stdout. By executing as a stateful process, gopls can maintain a
|
||||
significant amount of cache and can eagerly perform analysis on the source code
|
||||
being edited.
|
||||
|
||||
This execution mode does not work as well when there are many separate editor
|
||||
processes or when editor processes are short-lived, as is often the case for
|
||||
users of non-IDE editors such as Vim or Emacs. Having many processes means
|
||||
having many caches, consuming a significant amount of system resources. Using
|
||||
short-lived sessions means paying a start-up cost each time a session is
|
||||
created.
|
||||
|
||||
To support these types of workflows, a new mode of gopls execution is supported
|
||||
wherein a single, persistent, shared gopls "daemon" process is responsible for
|
||||
managing all gopls sessions. In this mode, editors still start a gopls sidecar,
|
||||
but this sidecar merely acts as a thin "forwarder", responsible for forwarding
|
||||
the LSP to the shared gopls instance and recording metrics, logs, and rpc
|
||||
traces.
|
||||
|
||||
## Quickstart
|
||||
|
||||
To use a shared gopls instance you must either manage the daemon process
|
||||
yourself, or let the gopls forwarder processes start the shared daemon as
|
||||
needed.
|
||||
|
||||
### Running with `-remote=auto`
|
||||
|
||||
Automatic management of the daemon is easiest, and can be done by passing the
|
||||
flag `-remote=auto` to the gopls process started by your editor. This will
|
||||
cause this process to auto-start the gopls daemon if needed, connect to it, and
|
||||
forward the LSP. For example, here is a reasonable gopls invocation, that sets
|
||||
some additional flags for easier [debugging](#debugging):
|
||||
|
||||
```bash
|
||||
gopls -remote=auto -logfile=auto -debug=:0 -remote.debug=:0 -rpc.trace
|
||||
```
|
||||
|
||||
Note that the shared gopls process will automatically shut down after one
|
||||
minute with no connected clients.
|
||||
|
||||
### Managing the daemon manually
|
||||
|
||||
To manage the gopls daemon process via external means rather than having the
|
||||
forwarders manage it, you must start a gopls daemon process with the
|
||||
`-listen=<addr>` flag, and then pass `-remote=<addr>` to the gopls processes
|
||||
started by your editor.
|
||||
|
||||
For example, to host the daemon on the TCP port `37374`, do:
|
||||
|
||||
```bash
|
||||
gopls -listen=:37374 -logfile=auto -debug=:0
|
||||
```
|
||||
|
||||
And then from the editor, run
|
||||
|
||||
```bash
|
||||
gopls -remote=:37374 -logfile=auto -debug=:0 -rpc.trace
|
||||
```
|
||||
|
||||
If you are on a POSIX system, you can also use unix domain sockets by prefixing
|
||||
the flag values with `unix;`. For example:
|
||||
|
||||
```bash
|
||||
gopls -listen="unix;/tmp/gopls-daemon-socket" -logfile=auto -debug=:0
|
||||
```
|
||||
|
||||
And connect via:
|
||||
|
||||
```bash
|
||||
gopls -remote="unix;/tmp/gopls-daemon-socket" -logfile=auto -debug=:0 -rpc.trace
|
||||
```
|
||||
|
||||
(Note that these flag values MUST be enclosed in quotes, because ';' is a
|
||||
special shell character. For this reason, this syntax is subject to change in
|
||||
the future.)
|
||||
|
||||
## Debugging
|
||||
|
||||
Debugging a shared gopls session is more complicated than a singleton session,
|
||||
because there are now two gopls processes involved with handling the LSP. Here
|
||||
are some tips:
|
||||
|
||||
### Finding logfiles and debug addresses
|
||||
|
||||
When running in daemon mode, you can use the `gopls inspect sessions` command
|
||||
to find the logfile and debug port for your gopls daemon instance (as well as
|
||||
for all its connected clients). By default, this inspects the default daemon
|
||||
(i.e. `-remote=auto`). To inspect a different daemon, use the `-remote` flag
|
||||
explicitly: `gopls -remote=localhost:12345 inspect sessions`.
|
||||
|
||||
This works whether or not you have enabled `-remote.debug`.
|
||||
|
||||
### Traversing debug pages
|
||||
|
||||
When `-debug=:0` is passed to gopls, it runs a webserver that serves stateful
|
||||
debug pages (see [troubleshooting.md](troubleshooting.md)). You can find the
|
||||
actual port hosting these pages by either using the `gopls inspect sessions`
|
||||
command, or by checking the start of the logfile -- it will be one of the first
|
||||
log messages. For example, if using `-logfile=auto`, find the debug address by
|
||||
checking `head /tmp/gopls-<pid>.log`.
|
||||
|
||||
By default, the gopls daemon is not started with `-debug`. To enable it, set
|
||||
the `-remote.debug` flag on the forwarder instance, so that it invokes gopls
|
||||
with `-debug` when starting the daemon.
|
||||
|
||||
The debug pages of the forwarder process will have a link to the debug pages of
|
||||
the daemon server process. Correspondingly, the debug pages of the daemon
|
||||
process will have a link to each of its clients.
|
||||
|
||||
This can help you find metrics, traces, and log files for all of the various
|
||||
servers and clients.
|
||||
|
||||
### Using logfiles
|
||||
|
||||
The gopls daemon is started with logging disabled by default. To customize
|
||||
this, pass `-remote.logfile` to the gopls forwarder. Using
|
||||
`-remote.logfile=auto`, the daemon will log to a default location (on posix
|
||||
systems: `/tmp/gopls-daemon-<pid>.log`).
|
||||
|
||||
The gopls daemon does not log session-scoped messages: those are instead
|
||||
reflected back to the forwarder so that they can be accessed by the editor.
|
||||
Daemon logs will only contain global messages, for example logs when sessions
|
||||
connect and disconnect.
|
||||
|
||||
It is recommended to start the forwarder gopls process with `-rpc.trace`, so
|
||||
that its logfile will contain rpc trace logs specific to the LSP session.
|
||||
|
||||
## Using multiple shared gopls instances
|
||||
|
||||
There may be environments where it is desirable to have more than one shared
|
||||
gopls instance. If managing the daemon manually, this can be done by simply
|
||||
choosing different `-listen` addresses for each distinct daemon process.
|
||||
|
||||
On POSIX systems, there is also support for automatic management of distinct
|
||||
shared gopls processes: distinct daemons can be selected by passing
|
||||
`-remote="auto;<id>"`. Any gopls forwarder passing the same value for `<id>`
|
||||
will use the same shared daemon.
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Why am I not saving as much memory as I expected when using a shared gopls?**
|
||||
|
||||
A: As described in [implementation.md](design/implementation.md), gopls has a
|
||||
concept of view/session/cache. Each session and view map onto exactly one
|
||||
editor session (because they contain things like edited but unsaved buffers).
|
||||
The cache contains things that are independent of any editor session, and can
|
||||
therefore be shared.
|
||||
|
||||
When, for example, three editor session are sharing a single gopls process,
|
||||
they will share the cache but will each have their own session and view. The
|
||||
memory savings in this mode, when compared to three separate gopls processes,
|
||||
corresponds to the amount of cache overlap across sessions.
|
||||
|
||||
Because this hasn't mattered much in the past, it is likely that there is state
|
||||
that can be moved out of the session/view, and into the cache, thereby
|
||||
increasing the amount of memory savings in the shared mode.
|
||||
|
||||
**Q: How do I customize the daemon instance when using `-remote=auto`?**
|
||||
|
||||
The daemon may be customized using flags of the form `-remote.*` on the
|
||||
forwarder gopls. This causes the forwarder to invoke gopls with these settings
|
||||
when starting the daemon. As of writing, we expose the following configuration:
|
||||
|
||||
* `-remote.logfile`: the location of the daemon logfile
|
||||
* `-remote.debug`: the daemon's debug address
|
||||
* `-remote.listen.timeout`: the amount of time the daemon should wait for new
|
||||
connections while there are no current connections, before shutting down.
|
||||
Must be set to a valid `time.Duration` (e.g. `30s` or `5m`). If `0`, listen
|
||||
indefinitely. Default: `1m`.
|
||||
|
||||
Note that once the daemon is already running, setting these flags will not
|
||||
change its configuration. These flags only matter for the forwarder process
|
||||
that actually starts the daemon.
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 370 KiB |
@@ -0,0 +1,436 @@
|
||||
# `gopls` design documentation
|
||||
|
||||
## _A note from the future_
|
||||
|
||||
What follows below is the original design document for gopls, aggregated from
|
||||
various sources spanning 2018 and 2019. Since then, all of the features listed
|
||||
below have been implemented, along with many others. The first two goals have
|
||||
been achieved: gopls is a full implementation of the LSP, and the default
|
||||
backend for VS Code Go and many other editors. The third goal has only been
|
||||
partially realized: while gopls has gained many features, it is not extensible
|
||||
in the sense used in this document: the only way to extend gopls is to modify
|
||||
gopls. The fourth goal is not achieved: while some notable companies are able
|
||||
to use gopls with Bazel, the experience is subpar, and the Go command is the
|
||||
only officially supported build system.
|
||||
|
||||
On the other hand, two of the explicit non-goals have been reconsidered. One is
|
||||
minor: syntax highlighting is now supported in the LSP by way of semantic
|
||||
tokens. The other is major: as gopls gained popularity, it became apparent that
|
||||
its memory footprint was a problem. The size of developer workspaces was
|
||||
increasing faster than the RAM available in typically development environments
|
||||
(particularly with containerized development). Gopls now uses a hybrid of
|
||||
on-disk indexes and in-memory caches, described in more detail in our
|
||||
[blog post on scalability](https://go.dev/blog/gopls-scalability).
|
||||
|
||||
Notably, in anticipating difficulties this doc turned out to be prescient.
|
||||
Gopls has indeed struggled against the core standary library packages upon
|
||||
which it is built, and its user experience is still limited by the LSP.
|
||||
Nevertheless, sticking with the standard library and LSP was the right
|
||||
approach, as despite our small team these decisions have helped gopls keep up
|
||||
with the evolving Go language (i.e. generics), and to integrate with many new
|
||||
text editors.
|
||||
|
||||
Gopls development continues, more than four years later, with a focus on
|
||||
simplicity, reliability, and extensibility. The new, opt-in
|
||||
[Go telemetry](https://github.com/golang/tools/releases/tag/gopls%2Fv0.14.0)
|
||||
will help us attain a higher standard of stability in our releases than we've
|
||||
been able to achieve through Github issues alone. Furthermore, telemetry will
|
||||
allow us to focus on high-priority features, and deprecate historical
|
||||
workarounds that burden the codebase. With greater velocity, we look forward
|
||||
to working with the community on improved refactoring, static analysis, and
|
||||
whatever else the future brings.
|
||||
|
||||
- _Rob Findley (rfindley@google.com), 2023_
|
||||
|
||||
## Goals
|
||||
|
||||
* `gopls` should **become the default editor backend** for the major editors used by Go programmers, fully supported by the Go team.
|
||||
* `gopls` will be a **full implementation of LSP**, as described in the [LSP specification], to standardize as many of its features as possible.
|
||||
* `gopls` will be **clean and extensible** so that it can encompass additional features in the future, allowing Go tooling to become best in class once more.
|
||||
* `gopls` will **support alternate build systems and file layouts**, allowing Go development to be simpler and more powerful in any environment.
|
||||
|
||||
## Context
|
||||
|
||||
While Go has a number of excellent and useful command-line tools that enhance the developer experience, it has become clear that integrating these tools with IDEs can pose challenges.
|
||||
|
||||
Support of these tools has relied on the goodwill of community members, and they have been put under a large burden of support at times as the language, toolchain and environments change. As a result many tools have ceased to work, have had support problems, or become confusing with forks and replacements, or provided an experience that is not as good as it could be.
|
||||
See the section below on [existing solutions](#existing-solutions) for more problems and details.
|
||||
|
||||
This is fine for tools used occasionally, but for core IDE features, this is not acceptable.
|
||||
Autocompletion, jump to definition, formatting, and other such features should always work, as they are key for Go development.
|
||||
|
||||
The Go team will create an editor backend that works in any build system.
|
||||
It will also be able to improve upon the latency of Go tools, since each tool will no longer have to individually run the type-checker on each invocation, instead there will be a long-running process and data can be shared between the definitions, completions, diagnostics, and other features.
|
||||
|
||||
By taking ownership of these tools and packaging them together in the form of gopls, the Go team will ensure that the Go development experience isn’t unnecessarily complicated for Go users.
|
||||
Having one editor backend will simplify the lives of Go developers, the Go team, and the maintainers of Go editor plugins.
|
||||
|
||||
See Rebecca's excellent GopherCon keynote [talk] and [slides] for some more context.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
* Command line speed
|
||||
|
||||
Although gopls will have a command line mode, it will be optimized for long running and not command responsiveness, as such it may not be the right tool for things like CI systems.
|
||||
For such cases there will have to be an alternate tool using the same underlying libraries for consistency.
|
||||
|
||||
* Low memory environments
|
||||
|
||||
In order to do a good job of processing large projects with very low latencies gopls will be holding a lot of information in memory.
|
||||
It is presumed that developers are normally working on systems with significant RAM and this will not be a problem.
|
||||
In general this is upheld by the large memory usage of existing IDE solutions (like IntelliJ)
|
||||
|
||||
* Syntax highlighting
|
||||
|
||||
At the moment there is no editor that delegates this functionality to a separate binary, and no standard way of doing it.
|
||||
|
||||
## Existing solutions
|
||||
|
||||
Every year the Go team conducts a survey, asking developers about their experiences with the language.
|
||||
|
||||
One question that is asked is “How do you feel about your editor?”.
|
||||
|
||||
The responses told a very negative story. Some categorized quotes:
|
||||
|
||||
* Setup
|
||||
* "Hard to install and configure"
|
||||
* "Inadequate documentation"
|
||||
* Performance
|
||||
* "Performance is very poor"
|
||||
* "Pretty slow in large projects"
|
||||
* Reliability
|
||||
* "Features work one day, but not the next"
|
||||
* "Tooling is not updated with new language features"
|
||||
|
||||
Each editor has its own plugin that shells out to a variety of tools, many of which break with new Go releases or because they are no longer maintained.
|
||||
|
||||
The individual tools each have to do the work to understand the code and all its transitive dependencies.
|
||||
|
||||
Each feature is a different tool, with a different set of patterns for its command line, a different way to accept input and parse output, a different way of specifying source code locations.
|
||||
To support its existing feature set, VSCode installed 24 different command line tools, many of which have options or forks to configure. When looking at the set of tools that needed to be migrated to modules, across all the editors, there were 63 separate tools.
|
||||
|
||||
All these tools need to understand the code, and they use the same standard libraries to do it. Those libraries are optimized for these kinds of tools, but even so processing that much code takes a lot of time time. Almost none of the tools are capable of returning results within 100ms.
|
||||
As developers type in their editor, multiple of these features need to activate, which means they are not just paying the cost once, but many times. The overall effect is an editing experience that feels sluggish, and features that are either not enabled or sometimes produce results that appear so slowly they are no longer useful when they arrive. This is a problem that increases with the size of the code base, which means it is getting worse over time, and is especially bad for the kinds of large code bases companies are dealing with as they use Go for more major tasks.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Complete feature set
|
||||
|
||||
For gopls to be considered a success it has to implement the full feature set discussed [below](#Features).
|
||||
This is the set of features that users need in order to feel as productive as they were with the tooling it is replacing. It does not include every feature of previous implementations, there are some features that are almost never used that should be dropped (like guru's pointer analysis) and some other features that do not easily fit and will have to be worked around (replacing the save hook/linter).
|
||||
|
||||
### Equivalent or better experience
|
||||
|
||||
For all of those features, the user experience must match or exceed the current one available in all editors.
|
||||
This is an easy statement to make, but a hard one to validate or measure. Many of the possible measures fail to capture the experience.
|
||||
|
||||
For instance, if an attempt was made to measure the latency of a jump to definition call, the results would be fairly consistent from the old godef tool. From the gopls implementation there may be a much larger range of latencies, with the best being orders of magnitude faster, and the worse slightly worse, because gopls attempts to do far more work, but manages to cache it across calls.
|
||||
|
||||
Or for a completion call, it might be slower but produce a better first match such that users accept it more often, resulting in an overall better experience.
|
||||
|
||||
For the most part this has to rely on user reports. If users are refusing to switch because the experience is not better, it is clearly not done, if they are switching but most people are complaining, there are probably enough areas that are better to make the switch compelling but other areas which are worse. If most people are switching and either staying silent or being positive, it is probably done. When writing tools, the user is all that matters.
|
||||
|
||||
### Solid community of contributors
|
||||
|
||||
The scope and scale of the problem gopls is trying to solve is untenable for the core Go team, it is going to require a strong community to make it all happen.
|
||||
|
||||
This implies the code must be easy to contribute to, and easy for many developers to work on in parallel. The functionality needs to be well decoupled, and have a thorough testing story.
|
||||
|
||||
### Latencies that fall within user tolerance
|
||||
|
||||
There has been a lot of research on acceptable latencies for user actions.
|
||||
<!-- TODO: research links -->
|
||||
The main result that affects gopls is that feedback in direct response to continuous user actions needs to be under 100ms to be imperceptible, and anything above 200ms aggravates the user.
|
||||
This means in general the aim has to be <100ms for anything that happens as the developer types.
|
||||
There will always be cases where gopls fails to meet this deadline, and there needs to be ways to make the user experience okay in those cases, but in general the point of this deadline is to inform the basic architecture design, any solution that cannot theoretically meet this goal in the long term is the wrong answer.
|
||||
|
||||
### Easy to configure
|
||||
|
||||
Developers are very particular, and have very differing desires in their coding experience. gopls is going to have to support a significant amount of flexibility, in order to meet those desires.
|
||||
The default settings however with no configuration at all must be the one that is best experience for most users, and where possible the features must be flexible without configuration so that the client can easily make the choices about treatment without changing its communication with gopls.
|
||||
|
||||
## Difficulties
|
||||
|
||||
### Volume of data
|
||||
|
||||
<!-- TODO: project sizes -->
|
||||
* Small:
|
||||
* Medium:
|
||||
* Large:
|
||||
* Corporate mono-repo: Much much bigger
|
||||
|
||||
Parsing and type checking large amounts of code is quite expensive, and the converted forms use a lot of space. As gopls has to keep updating this information while the developer types, it needs to manage how it caches the converted forms very carefully to balance memory use vs speed.
|
||||
|
||||
### Cache invalidation
|
||||
|
||||
The basic unit of operation for the type checking is the package, but the basic unit of operation for an editor is the file.
|
||||
gopls needs to be able to map files to packages efficiently, so that when files change it knows which packages need to be updated (along with any other packages that transitively depended on them).
|
||||
This is made especially difficult by the fact that changing the content of a file can modify which packages it is considered part of (either by changing the package declaration or the build tags), a file can be in more than one package, and changes can be made to files without using the editor, in which case it will not notify us of the changes.
|
||||
|
||||
### Inappropriate core functionality
|
||||
|
||||
The base libraries for Go (things like [go/token], [go/ast] and [go/types]) are all designed for compiler-like applications.
|
||||
They tend to worry more about throughput than memory use, they have structures that are intended to grow and then be thrown away at program exit, and they are not designed to keep going in the presence of errors in the source they are handling.
|
||||
They also have no abilities to do incremental changes.
|
||||
|
||||
Making a long running service work well with those libraries is a very large challenge, but writing new libraries would be far more work, and cause a significant long term cost as both sets of libraries would have to be maintained. Right now it is more important to get a working tool into the hands of users. In the long term this decision may have to be revisited, new low level libraries may be the only way to keep pushing the capabilities forwards.
|
||||
|
||||
### Build system capabilities
|
||||
|
||||
gopls is supposed to be build system agnostic, but it must use the build system to discover how files map to packages. When it tries to do so, even when the functionality is the same, the costs (in time, CPU and memory) are very different, and can significantly impact the user experience. Designing how gopls interacts with the build system to try to minimize or hide these differences is hard.
|
||||
|
||||
### Build tags
|
||||
|
||||
The build tag system in Go is quite powerful, and has many use cases. Source files can exclude themselves using powerful boolean logic on the set of active tags.
|
||||
It is however designed for specifying the set of active tags on the command line, and the libraries are all designed to cope with only one valid combination at a time. There is also no way to work out the set of valid combinations.
|
||||
|
||||
Type checking a file requires knowledge of all the other files in the same package, and that set of files is modified by the build tags. The set of exported identifiers of a package is also affected by which files are in the package, and thus its build tags.
|
||||
|
||||
This means that even for files or packages that have no build tag controls it is not possible to produce correct results without knowing the set of build tags to consider.
|
||||
This makes it very hard to produce useful results when viewing a file.
|
||||
|
||||
### Features not supported by LSP
|
||||
|
||||
There are some things it would be good to be able to do that do not fit easily into the existing LSP protocol.
|
||||
For instance, displaying control flow information, automatic struct tags, complex refactoring...
|
||||
|
||||
Each feature will have to be considered carefully, and either propose a change to LSP, or add a way to have gopls specific extensions to the protocol that are still easy to use in all the editor plugins.
|
||||
|
||||
To avoid these at the start, only core LSP features will be implemented, as they are sufficient to meet the baseline requirements anyway, but the potential features need to be kept in mind in the core architecture.
|
||||
|
||||
### Distribution
|
||||
|
||||
Making sure that users are using the right version of gopls is going to be a problem. Each editor plugin is probably going to install the tools in its own way, some will choose to install it system wide, some will keep their own copy.
|
||||
|
||||
Because it is a brand new tool, it will be changing rapidly. If users are not informed they are on an old version they will be experiencing problems that have already been fixed, which is worse for them, and then probably reporting them, which wastes time for the gopls team. There needs to be a mechanism for gopls to check if is up to date, and a recommended way to install an up to date version.
|
||||
|
||||
### Debugging user problems
|
||||
|
||||
gopls is essentially a very stateful long running server on the developer's machine. Its basic operation is affected by many things, from the users environment to the contents of the local build cache. The data it is operating on is often a confidential code base that cannot be shared.
|
||||
All of these things make it hard for users to report a bug usefully, or create a minimal reproduction.
|
||||
|
||||
There needs to be easy ways for users to report what information they can, and ways to attempt to reproduce problems without their entire state. This is also needed to produce regression tests.
|
||||
|
||||
## Basic design decisions
|
||||
|
||||
There are some fundamental architecture decisions that affect much of the rest of the design of the tool, making fundamental trade offs that impact the user experience.
|
||||
|
||||
### Process lifetime: *managed by the editor*
|
||||
|
||||
Processing a large code base to fully type check and then analyze it within the latency requirements is not feasible, and is one of the primary problems with the existing solutions. This remains true even if the computed information was cached on disk, as running analyzers and type checkers ends up requiring the full AST of all files in the dependency graph.
|
||||
It is theoretically possible to do better, but only with a major re-write of the existing parsing and type checking libraries, something that is not feasible at this time.
|
||||
|
||||
This implies that gopls should be a long running process, that is able to cache and pre-calculate results in memory so that when a request arrives it can produce the answer much faster.
|
||||
|
||||
It could run as a daemon on the user's machine, but there are a lot of issues with managing a daemon. It may well be the right choice in the long term, and it should be allowed for in the fundamental architecture design, but to start with it will instead have a process that lasts as long as the editor that starts it, and that can easily be restarted.
|
||||
|
||||
### Caching: *in memory*
|
||||
|
||||
Persistent disk caches are very expensive to maintain, and require solving a lot of extra problems.
|
||||
Although building the information required is expensive compared to the latencies required of the requests, it is fairly minor compared to the startup times of an editor, so it is expected that rebuilding the information when gopls is restarted will be acceptable.
|
||||
|
||||
The advantage gained from this is that gopls becomes stateless across restarts which means if it has issues or gets its state confused, a simple restart will often fix the problem.
|
||||
It also means that when users report problems, the entire state of the on disk cache is not needed to diagnose and reproduce the issue.
|
||||
|
||||
### Communication: *stdin/stdout JSON*
|
||||
|
||||
The LSP specification defines the JSON messages that are normally used, but it does not define how those message should be sent, and there are implementations of the LSP that do not use JSON (for instance, Protocol buffers are an option).
|
||||
|
||||
The constraints on gopls are that it must be easy to integrate into *every editor* on *all operating systems*, and that it should not have large external dependencies.
|
||||
|
||||
JSON is part of the Go standard library, and is also the native language of LSP, so it makes the most sense. By far the best supported communication mechanism is the standard input and output of a process, and the common client implementations all have ways of using [JSON rpc 2] in this mode. There were no complete and low dependency implementations of this protocol in Go, but it is a fairly small protocol on top of the JSON library that can be implemented with a moderate effort, and would be a generally useful library to have anyway.
|
||||
|
||||
In the future it is expected to run in separated client server mode, so writing it in a way that could use sockets instead of stdin/stdout from the start was the best way to make sure it remained possible. It was also a huge debugging aid to be able to run the gopls server by hand and watch/debug it outside the editor.
|
||||
|
||||
### Running other tools: *no*
|
||||
|
||||
<!--- TODO: subprocess discuss --->
|
||||
|
||||
## Features
|
||||
|
||||
<!--TODO(rstambler): Generate a file that lists all of the supported features.-->
|
||||
|
||||
There is a set of features that gopls needs to expose to be a comprehensive IDE solution.
|
||||
The following is the minimum set of features, along with their existing solutions and how they should map to the LSP.
|
||||
|
||||
### Introspection
|
||||
|
||||
Introspection features tell developers information about their code while they work. They do not make or suggest changes.
|
||||
|
||||
---
|
||||
Diagnostics | Static analysis results of the code, including compilation and lint errors
|
||||
----------- | ---
|
||||
Requires | Full go/analysis run, which needs full AST, type and SSA information
|
||||
LSP | [`textDocument/publishDiagnostics`]
|
||||
Previous | `go build`, `go vet`, `golint`, [errcheck], [staticcheck] <!-- TODO: and all the rest -->
|
||||
| | This is one of the most important IDE features, allowing fast turn around without having to run compilers and checkers in the shell. Often used to power problem lists, gutter markers and squiggle underlines in the IDE. <br/> There is some complicated design work to do in order to let users customize the set of checks being run, preferably without having to recompile the main LSP binary.
|
||||
|
||||
---
|
||||
Hover | Information about the code under the cursor.
|
||||
-------- | ---
|
||||
Requires | AST and type information for the file and all dependencies
|
||||
LSP | [`textDocument/hover`]
|
||||
Previous | [godoc], [gogetdoc]
|
||||
| | Used when reading code to display information known to the compiler but not always obvious from the code. For instance it may return the types of identifiers, or the documentation.
|
||||
|
||||
---
|
||||
Signature help | Function parameter information and documentation
|
||||
-------------- | ---
|
||||
Requires | AST and type information for the file and all dependencies
|
||||
LSP | [`textDocument/signatureHelp`]
|
||||
Previous | [gogetdoc]
|
||||
| | As a function call is being typed into code, it is helpful to know the parameters of that call to enable the developer to call it correctly.
|
||||
|
||||
### Navigation
|
||||
|
||||
Navigation features are designed to make it easier for a developer to find their way round a code base.
|
||||
|
||||
---
|
||||
Definition | Select an identifier, and jump to the code where that identifier was defined.
|
||||
---------- | ---
|
||||
Requires | Full type information for file and all dependencies
|
||||
LSP | [`textDocument/declaration`]
|
||||
| | [`textDocument/definition`]
|
||||
| | [`textDocument/typeDefinition`]
|
||||
Previous | [godef] |
|
||||
| | Asking the editor to open the place where a symbol was defined is one of the most commonly used code navigation tools inside an IDE when available. It is especially valuable when exploring an unfamiliar code base.<br/>Due to a limitation of the compiler output, it is not possible to use the binary data for this task (specifically it does not know column information) and thus it must parse from source.
|
||||
|
||||
---
|
||||
Implementation | Reports the types that implement an interface
|
||||
-------------- | ---
|
||||
Requires | Full workspace type knowledge
|
||||
LSP | [`textDocument/implementation`]
|
||||
Previous | [impl]
|
||||
| | This feature is hard to scale up to large code bases, and is going to take thought to get right. It may be feasible to implemented a more limited form in the meantime.
|
||||
|
||||
---
|
||||
Document symbols | Provides the set of top level symbols in the current file.
|
||||
---------------- | ---
|
||||
Requires | AST of the current file only
|
||||
LSP | [`textDocument/documentSymbol`]
|
||||
Previous | [go-outline], [go-symbols]
|
||||
| | Used to drive things like outline mode.
|
||||
|
||||
---
|
||||
References | Find all references to the symbol under the cursor.
|
||||
---------- | ---
|
||||
Requires | AST and type information for the **reverse** transitive closure
|
||||
LSP | [`textDocument/references`]
|
||||
Previous | [guru]
|
||||
| | This requires knowledge of every package that could possible depend on any packages the current file is part of. In the past this has been implemented either by global knowledge, which does not scale, or by specifying a "scope" which confused users to the point where they just did not use the tools. gopls is probably going to need a more powerful solution in the long term, but to start with automatically limiting the scope may produce acceptable results. This would probably be the module if known, or some sensible parent directory otherwise.
|
||||
|
||||
---
|
||||
Folding | Report logical hierarchies of blocks
|
||||
-------- | ---
|
||||
Requires | AST of the current file only
|
||||
LSP | [`textDocument/foldingRange`]
|
||||
Previous | [go-outline]
|
||||
| | This is normally used to provide expand and collapse behavior in editors.
|
||||
|
||||
---
|
||||
Selection | Report regions of logical selection around the cursor
|
||||
--------- | ---
|
||||
Requires | AST of the current file only
|
||||
LSP | [`textDocument/selectionRange`]
|
||||
Previous | [guru]
|
||||
| | Used in editor features like expand selection.
|
||||
|
||||
|
||||
### Edit assistance
|
||||
|
||||
These features suggest or apply edits to the code for the user, including refactoring features, for which there are many potential use cases.
|
||||
Refactoring is one of the places where Go tools could potentially be very strong, but have not been so far, and thus there is huge potential for improvements in the developer experience.
|
||||
There is not yet a clear understanding of the kinds of refactoring people need or how they should express them however, and there are weaknesses in the LSP protocol around this.
|
||||
This means it may be much more of a research project.
|
||||
|
||||
|
||||
---
|
||||
Format | Fix the formatting of the file
|
||||
-------- | ---
|
||||
Requires | AST of current file
|
||||
LSP | [`textDocument/formatting`]
|
||||
| | [`textDocument/rangeFormatting`]
|
||||
| | [`textDocument/onTypeFormatting`]
|
||||
Previous | [gofmt], [goimports], [goreturns]
|
||||
| | It will use the standard format package. <br/> Current limitations are that it does not work on malformed code. It may need some very careful changes to the formatter to allow for formatting an invalid AST or changes to force the AST to a valid mode. These changes would improve range and file mode as well, but are basically vital to onTypeFormatting
|
||||
|
||||
---
|
||||
Imports | Rewrite the imports block automatically to match the symbols used.
|
||||
-------- | ---
|
||||
Requires | AST of the current file and full symbol knowledge for all candidate packages.
|
||||
LSP | [`textDocument/codeAction`]
|
||||
Previous | [goimports], [goreturns]
|
||||
| | This needs knowledge of packages that are not yet in use, and the ability to find those packages by name. <br/> It also needs exported symbol information for all the packages it discovers. <br/> It should be implemented using the standard imports package, but there may need to be exposed a more fine grained API than just a file rewrite for some of the interactions.
|
||||
|
||||
---
|
||||
Autocompletion | Makes suggestions to complete the entity currently being typed.
|
||||
-------------- | ---
|
||||
Requires | AST and type information for the file and all dependencies<br/> Also full exported symbol knowledge for all packages.
|
||||
LSP | [`textDocument/completion`]
|
||||
| | [`completionItem/resolve`]
|
||||
Previous | [gocode]
|
||||
| | Autocomplete is one of the most complicated features, and the more it knows the better its suggestions can be. For instance it can autocomplete into packages that are not yet being imported if it has their public symbols. It can make better suggestions of options if it knows what kind of program you are writing. It can suggest better arguments if it knows how you normally call a function. It can suggest entire patterns of code if it knows they are common. Unlike many other features, which have a specific task, and once it is doing that task the feature is done, autocomplete will never be finished. Balancing and improving both the candidates and how they are ranked will be a research problem for a long time to come.
|
||||
|
||||
---
|
||||
Rename | Rename an identifier
|
||||
-------- | ---
|
||||
Requires | AST and type information for the **reverse** transitive closure
|
||||
LSP | [`textDocument/rename`]
|
||||
| | [`textDocument/prepareRename`]
|
||||
Previous | [gorename]
|
||||
| | This uses the same information that find references does, with all the same problems and limitations. It is slightly worse because the changes it suggests make it intolerant of incorrect results. It is also dangerous using it to change the public API of a package.
|
||||
|
||||
---
|
||||
Suggested fixes | Suggestions that can be manually or automatically accepted to change the code
|
||||
--------------- | ---
|
||||
Requires | Full go/analysis run, which needs full AST, type and SSA information
|
||||
LSP | [`textDocument/codeAction`]
|
||||
Previous | N/A
|
||||
| | This is a brand new feature powered by the new go/analysis engine, and it should allow a huge amount of automated refactoring.
|
||||
|
||||
[LSP specification]: https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/
|
||||
[talk]: TODO
|
||||
[slides]: https://github.com/gophercon/2019-talks/blob/master/RebeccaStambler-GoPleaseStopBreakingMyEditor/slides.pdf "Go, please stop breaking my editor!"
|
||||
[JSON rpc 2]: https://www.jsonrpc.org/specification
|
||||
|
||||
[errcheck]: https://github.com/kisielk/errcheck
|
||||
[go-outline]: https://github.com/lukehoban/go-outline
|
||||
[go-symbols]: https://github.com/acroca/go-symbols
|
||||
[gocode]: https://github.com/stamblerre/gocode
|
||||
[godef]: https://github.com/rogpeppe/godef
|
||||
[godoc]: https://golang.org/cmd/godoc
|
||||
[gofmt]: https://golang.org/cmd/gofmt
|
||||
[gogetdoc]: https://github.com/zmb3/gogetdoc
|
||||
[goimports]: https://pkg.go.dev/golang.org/x/tools/cmd/goimports
|
||||
[gorename]: https://pkg.go.dev/golang.org/x/tools/cmd/gorename
|
||||
[goreturns]: https://github.com/sqs/goreturns
|
||||
[gotags]: https://github.com/jstemmer/gotags
|
||||
[guru]: https://pkg.go.dev/golang.org/x/tools/cmd/guru
|
||||
[impl]: https://github.com/josharian/impl
|
||||
[staticcheck]: https://staticcheck.io/docs/
|
||||
[go/types]: https://golang.org/pkg/go/types/
|
||||
[go/ast]: https://golang.org/pkg/go/ast/
|
||||
[go/token]: https://golang.org/pkg/go/token/
|
||||
|
||||
[`completionItem/resolve`]:https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#completionItem_resolve
|
||||
[`textDocument/codeAction`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_codeAction
|
||||
[`textDocument/completion`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_completion
|
||||
[`textDocument/declaration`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_declaration
|
||||
[`textDocument/definition`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_definition
|
||||
[`textDocument/documentLink`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_documentLink
|
||||
[`textDocument/documentSymbol`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_documentSymbol
|
||||
[`textDocument/foldingRange`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_foldingRange
|
||||
[`textDocument/formatting`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_formatting
|
||||
[`textDocument/highlight`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_highlight
|
||||
[`textDocument/hover`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_hover
|
||||
[`textDocument/implementation`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_implementation
|
||||
[`textDocument/onTypeFormatting`]:https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_onTypeFormatting
|
||||
[`textDocument/prepareRename`]:https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_prepareRename
|
||||
[`textDocument/publishDiagnostics`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_publishDiagnostics
|
||||
[`textDocument/rangeFormatting`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_rangeFormatting
|
||||
[`textDocument/references`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_references
|
||||
[`textDocument/rename`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_rename
|
||||
[`textDocument/selectionRange`]:https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_selectionRange
|
||||
[`textDocument/signatureHelp`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_signatureHelp
|
||||
[`textDocument/typeDefinition`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_typeDefinition
|
||||
[`workspace/didChangeWatchedFiles`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#workspace_didChangeWatchedFiles
|
||||
@@ -0,0 +1,172 @@
|
||||
|
||||
# Gopls architecture
|
||||
|
||||
Last major update: Jan 16 2024
|
||||
|
||||
This doc presents a high-level overview of the structure of gopls to
|
||||
help new contributors find their way. It is not intended to be a
|
||||
complete description of the implementation, nor even of any key
|
||||
components; for that, the package documentation (linked below) and
|
||||
other comments within the code are a better guide.
|
||||
|
||||
The diagram below shows selected components of the gopls module and
|
||||
their relationship to each other according to the Go import graph.
|
||||
Tests and test infrastructure are not shown, nor are utility packages,
|
||||
nor packages from the [x/tools] module. For brevity, packages are
|
||||
referred to by their last segment, which is usually unambiguous.
|
||||
|
||||
The height of each blob corresponds loosely to its technical depth.
|
||||
Some blocks are wide and shallow, such as [protocol], which declares
|
||||
Go types for the entire LSP protocol. Others are deep, such as [cache]
|
||||
and [golang], as they contain a lot of dense logic and algorithms.
|
||||
|
||||
<!-- Source: https://docs.google.com/drawings/d/1CK6YSLt7G3svRoZf7skJI-lxRol2VI90YOxHcYS0DP4 -->
|
||||

|
||||
|
||||
Starting from the bottom, we'll describe the various components.
|
||||
|
||||
The lowest layer defines the request and response types of the
|
||||
Language Server Protocol:
|
||||
|
||||
- The [protocol] package defines the standard protocol; it is mostly
|
||||
generated mechanically from the schema definition provided by
|
||||
Microsoft.
|
||||
The most important type is DocumentURI, which represents a `file:`
|
||||
URL that identifies a client editor document. It also provides
|
||||
`Mapper`, which maps between the different coordinate systems used
|
||||
for source positions: UTF-8, UTF-16, and token.Pos.
|
||||
|
||||
- The [command] package defines Gopls's non-standard commands, which
|
||||
are all invoked through the `workspace/executeCommand` extension
|
||||
mechanism. These commands are typically returned by the server as
|
||||
continuations of Code Actions or Code Lenses; most clients do not
|
||||
construct calls to them directly.
|
||||
|
||||
The next layer defines a number of important and very widely used data structures:
|
||||
|
||||
- The [file] package defines the primary abstractions of a client
|
||||
file: its `Identity` (URI and content hash), and its `Handle` (which
|
||||
additionally provides the version and content of a particular
|
||||
snapshot of the file.
|
||||
|
||||
- The [parsego] package defines `File`, the parsed form of a Go source
|
||||
file, including its content, syntax tree, and coordinary mappings
|
||||
(Mapper and token.File). The package performs various kinds of tree
|
||||
repair to work around error-recovery shortcomings of the Go parser.
|
||||
|
||||
- The [metadata] package defines `Package`, an abstraction of the
|
||||
metadata of a Go package, similar to the output of `go list -json`.
|
||||
Metadata is produced from [go/packages], which takes
|
||||
care of invoking `go list`. (Users report that it works to some extent
|
||||
with a GOPACKAGESDRIVER for Bazel, though we maintain no tests for this
|
||||
scenario.)
|
||||
|
||||
The package also provides `Graph`, the complete import graph for a
|
||||
workspace; each graph node is a `Package`.
|
||||
|
||||
The [settings] layer defines the data structure (effectively a large
|
||||
tree) for gopls configuration options, along with its JSON encoding.
|
||||
|
||||
The [cache] layer is the largest and most complex component of gopls.
|
||||
It is concerned with state management, dependency analysis, and invalidation:
|
||||
the `Session` of communication with the client;
|
||||
the `Folder`s that the client has opened;
|
||||
the `View` of a particular workspace tree with particular build
|
||||
options;
|
||||
the `Snapshot` of the state of all files in the workspace after a
|
||||
particular edit operation;
|
||||
the contents of all files, whether saved to disk (`DiskFile`) or
|
||||
edited and unsaved (`Overlay`);
|
||||
the `Cache` of in-memory memoized computations,
|
||||
such as parsing go.mod files or build the symbol index;
|
||||
and the `Package`, which holds the results of type checking a package
|
||||
from Go syntax.
|
||||
|
||||
The cache layer depends on various auxiliary packages, including:
|
||||
|
||||
- The [filecache] package, which manages gopls' persistent, transactional,
|
||||
file-based key/value store.
|
||||
|
||||
- The [xrefs], [methodsets], and [typerefs] packages define algorithms
|
||||
for constructing indexes of information derived from type-checking,
|
||||
and for encoding and decoding these serializable indexes in the file
|
||||
cache.
|
||||
|
||||
Together these packages enable the fast restart, reduced memory
|
||||
consumption, and synergy across processes that were delivered by the
|
||||
v0.12 redesign and described in ["Scaling gopls for the growing Go
|
||||
ecosystem"](https://go.dev/blog/gopls-scalability).
|
||||
|
||||
The cache also defines gopls's [go/analysis] driver, which runs
|
||||
modular analysis (similar to `go vet`) across the workspace.
|
||||
Gopls also includes a number of analysis passes that are not part of vet.
|
||||
|
||||
The next layer defines four packages, each for handling files in a
|
||||
particular language:
|
||||
[mod] for go.mod files;
|
||||
[work] for go.work files;
|
||||
[template] for files in `text/template` syntax; and
|
||||
[golang], for files in Go itself.
|
||||
This package, by far the largest, provides the main features of gopls:
|
||||
navigation, analysis, and refactoring of Go code.
|
||||
As most users imagine it, this package _is_ gopls.
|
||||
|
||||
The [server] package defines the LSP service implementation, with one
|
||||
handler method per LSP request type. Each handler switches on the type
|
||||
of the file and dispatches to one of the four language-specific
|
||||
packages.
|
||||
|
||||
The [lsprpc] package connects the service interface to our [JSON RPC](jsonrpc2)
|
||||
server.
|
||||
|
||||
Bear in mind that the diagram is a dependency graph, a "static"
|
||||
viewpoint of the program's structure. A more dynamic viewpoint would
|
||||
order the packages based on the sequence in which they are encountered
|
||||
during processing of a particular request; in such a view, the bottom
|
||||
layer would represent the "wire" (protocol and command), the next
|
||||
layer up would hold the RPC-related packages (lsprpc and server), and
|
||||
features (e.g. golang, mod, work, template) would be at the top.
|
||||
|
||||
<!--
|
||||
A dynamic view would be an interesting topic for another article.
|
||||
This slide deck [requires Google network]
|
||||
The Life of a (gopls) Query (Oct 2021)
|
||||
https://docs.google.com/presentation/d/1c8XJaIldzii-F3YvEOPWHK_MQJ_o8ua5Bct1yDa3ZlU
|
||||
provides useful (if somewhat out of date) information.
|
||||
-->
|
||||
|
||||
The [cmd] package defines the command-line interface of the `gopls`
|
||||
command, around which gopls's main package is just a trivial wrapper.
|
||||
It is usually run without arguments, causing it to start a server and
|
||||
listen indefinitely.
|
||||
It also provides a number of subcommands that start a server, make a
|
||||
single request to it, and exit, providing traditional batch-command
|
||||
access to server functionality. These subcommands are primarily
|
||||
provided as a debugging aid (but see
|
||||
[#63693](https://github.com/golang/go/issues/63693)).
|
||||
|
||||
[cache]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/cache
|
||||
[cmd]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/cmd
|
||||
[command]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/protocol/command
|
||||
[debug]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/debug
|
||||
[file]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/file
|
||||
[filecache]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/filecache
|
||||
[go/analysis]: https://pkg.go.dev/golang.org/x/tools@master/go/analysis
|
||||
[go/packages]: https://pkg.go.dev/golang.org/x/tools@master/go/packages
|
||||
[gopls]: https://pkg.go.dev/golang.org/x/tools/gopls@master
|
||||
[jsonrpc2]: https://pkg.go.dev/golang.org/x/tools@master/internal/jsonrpc2
|
||||
[lsprpc]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/lsprpc
|
||||
[memoize]: https://github.com/golang/tools/tree/master/internal/memoize
|
||||
[metadata]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/cache/metadata
|
||||
[methodsets]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/cache/methodsets
|
||||
[mod]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/mod
|
||||
[parsego]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/cache/parsego
|
||||
[protocol]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/protocol
|
||||
[server]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/server
|
||||
[settings]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/settings
|
||||
[golang]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/golang
|
||||
[template]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/template
|
||||
[typerefs]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/cache/typerefs
|
||||
[work]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/work
|
||||
[x/tools]: https://github.com/golang/tools@master
|
||||
[xrefs]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/cache/xrefs
|
||||
@@ -0,0 +1,89 @@
|
||||
# Documentation for plugin authors
|
||||
|
||||
If you are integrating `gopls` into an editor by writing an editor plugin, there are quite a few semantics of the communication between the editor and `gopls` that are not specified by the [LSP specification].
|
||||
|
||||
We attempt to document those details along with any other information that has been helpful to other plugin authors here.
|
||||
|
||||
If you are implementing a plugin yourself and have questions this page does not answer, please reach out to us to ask, and then also contribute your findings back to this page.
|
||||
|
||||
## Supported features
|
||||
|
||||
For the most part you should look at the [list](status.md#supported-features) in the current status document to know if gopls supports a feature.
|
||||
For a truly authoritative answer you should check the [result][InitializeResult] of the [initialize] request, where gopls enumerates its support in the [ServerCapabilities].
|
||||
|
||||
|
||||
## Positions and ranges
|
||||
|
||||
Many LSP requests pass position or range information. This is described in the [LSP specification][lsp-text-documents]:
|
||||
|
||||
> A position inside a document (see Position definition below) is expressed as a zero-based line and character offset. The offsets are based on a UTF-16 string representation. So a string of the form a𐐀b the character offset of the character a is 0, the character offset of 𐐀 is 1 and the character offset of b is 3 since 𐐀 is represented using two code units in UTF-16.
|
||||
|
||||
This means that integrators will need to calculate UTF-16 based column offsets.
|
||||
Use `protocol.Mapper` for all the conversions.
|
||||
|
||||
## Edits
|
||||
|
||||
In order to deliver changes from gopls to the editor, the LSP supports arrays of [`TextEdit`][lsp-textedit]s in responses.
|
||||
The spec specifies exactly how these should be applied:
|
||||
|
||||
> All text edits ranges refer to positions in the original document. Text edits ranges must never overlap, that means no part of the original document must be manipulated by more than one edit. However, it is possible that multiple edits have the same start position: multiple inserts, or any number of inserts followed by a single remove or replace edit. If multiple inserts have the same position, the order in the array defines the order in which the inserted strings appear in the resulting text.
|
||||
|
||||
All `[]TextEdit` are sorted such that applying the array of deltas received in reverse order achieves the desired result that holds with the spec.
|
||||
|
||||
## Errors
|
||||
|
||||
Various error codes are described in the [LSP specification][lsp-response]. We are still determining what it means for a method to return an error; are errors only for low-level LSP/transport issues or can other conditions cause errors to be returned? See some of this discussion on [#31526].
|
||||
|
||||
The method chosen is currently influenced by the exact treatment in the currently popular editor integrations. It may well change, and ideally would become more coherent across requests.
|
||||
|
||||
* [`textDocument/codeAction`]: Return error if there was an error computing code actions.
|
||||
* [`textDocument/completion`]: Log errors, return empty result list.
|
||||
* [`textDocument/definition`]: Return error if there was an error computing the definition for the position.
|
||||
* [`textDocument/typeDefinition`]: Return error if there was an error computing the type definition for the position.
|
||||
* [`textDocument/formatting`]: Return error if there was an error formatting the file.
|
||||
* [`textDocument/highlight`]: Log errors, return empty result.
|
||||
* [`textDocument/hover`]: Return empty result.
|
||||
* [`textDocument/documentLink`]: Log errors, return nil result.
|
||||
* [`textDocument/publishDiagnostics`]: Log errors if there were any while computing diagnostics.
|
||||
* [`textDocument/references`]: Log errors, return empty result.
|
||||
* [`textDocument/rename`]: Return error if there was an error computing renames.
|
||||
* [`textDocument/signatureHelp`]: Log errors, return nil result.
|
||||
* [`textDocument/documentSymbols`]: Return error if there was an error computing document symbols.
|
||||
|
||||
## Watching files
|
||||
|
||||
It is fairly normal for files that affect `gopls` to be modified outside of the editor it is associated with.
|
||||
|
||||
For instance, files that are needed to do correct type checking are modified by switching branches in git, or updated by a code generator.
|
||||
|
||||
Monitoring files inside gopls directly has a lot of awkward problems, but the [LSP specification] has methods that allow gopls to request that the client notify it of file system changes, specifically [`workspace/didChangeWatchedFiles`].
|
||||
This is currently being added to gopls by a community member, and tracked in [#31553]
|
||||
|
||||
[InitializeResult]: https://pkg.go.dev/golang.org/x/tools/gopls/internal/protocol#InitializeResult
|
||||
[ServerCapabilities]: https://pkg.go.dev/golang.org/x/tools/gopls/internal/protocol#ServerCapabilities
|
||||
[`golang.org/x/tools/gopls/internal/protocol`]: https://pkg.go.dev/golang.org/x/tools/internal/protocol#NewPoint
|
||||
|
||||
[LSP specification]: https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/
|
||||
[lsp-response]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#response-message
|
||||
[initialize]: https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#initialize
|
||||
[lsp-text-documents]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#text-documents
|
||||
[lsp-textedit]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textedit
|
||||
|
||||
[`textDocument/codeAction`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_codeAction
|
||||
[`textDocument/completion`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_completion
|
||||
[`textDocument/definition`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_definition
|
||||
[`textDocument/typeDefinition`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_typeDefinition
|
||||
[`textDocument/formatting`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_formatting
|
||||
[`textDocument/highlight`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_highlight
|
||||
[`textDocument/hover`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_hover
|
||||
[`textDocument/documentLink`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_documentLink
|
||||
[`textDocument/publishDiagnostics`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_publishDiagnostics
|
||||
[`textDocument/references`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_references
|
||||
[`textDocument/rename`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_rename
|
||||
[`textDocument/signatureHelp`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_signatureHelp
|
||||
[`textDocument/documentSymbols`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_documentSymbols
|
||||
[`workspace/didChangeWatchedFiles`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#workspace_didChangeWatchedFiles
|
||||
|
||||
[#31080]: https://github.com/golang/go/issues/31080
|
||||
[#31553]: https://github.com/golang/go/issues/31553
|
||||
[#31526]: https://github.com/golang/go/issues/31526
|
||||
@@ -0,0 +1,185 @@
|
||||
# Emacs
|
||||
|
||||
## Installing `gopls`
|
||||
|
||||
To use `gopls` with Emacs, you must first
|
||||
[install the `gopls` binary](../README.md#installation) and ensure that the directory
|
||||
containing the resulting binary (either `$(go env GOBIN)` or `$(go env
|
||||
GOPATH)/bin`) is in your `PATH`.
|
||||
|
||||
## Choosing an Emacs LSP client
|
||||
|
||||
To use `gopls` with Emacs, you will need to choose and install an Emacs LSP
|
||||
client package. Two popular client packages are [LSP Mode] and [Eglot].
|
||||
|
||||
LSP Mode takes a batteries-included approach, with many integrations enabled
|
||||
“out of the box” and several additional behaviors provided by `lsp-mode` itself.
|
||||
|
||||
Eglot takes a minimally-intrusive approach, focusing on smooth integration with
|
||||
other established packages. It provides a few of its own `eglot-` commands but
|
||||
no additional keybindings by default.
|
||||
|
||||
Once you have selected which client you want to use, install it per the packages
|
||||
instructions: see [Eglot 1-2-3](https://github.com/joaotavora/eglot#1-2-3) or
|
||||
[LSP Mode Installation](https://emacs-lsp.github.io/lsp-mode/page/installation/).
|
||||
|
||||
## Common configuration
|
||||
|
||||
Both Eglot and LSP Mode can integrate with popular packages in the Emacs
|
||||
ecosystem:
|
||||
|
||||
* The built-in [`xref`] package provides cross-references.
|
||||
* The built-in [Flymake] package provides an on-the-fly diagnostic overlay.
|
||||
* [Company] mode displays code completion candidates (with a richer UI than
|
||||
the built-in [`completion-at-point`]).
|
||||
|
||||
Eglot provides documentation using the built-in [ElDoc] minor mode, while LSP
|
||||
Mode by default provides documentation using its own [`lsp-ui`] mode.
|
||||
|
||||
Eglot by default locates the project root using the [`project`] package. In LSP
|
||||
Mode, this behavior can be configured using the `lsp-auto-guess-root` setting.
|
||||
|
||||
## Configuring LSP Mode
|
||||
|
||||
### Loading LSP Mode in `.emacs`
|
||||
|
||||
```elisp
|
||||
(require 'lsp-mode)
|
||||
(add-hook 'go-mode-hook #'lsp-deferred)
|
||||
|
||||
;; Set up before-save hooks to format buffer and add/delete imports.
|
||||
;; Make sure you don't have other gofmt/goimports hooks enabled.
|
||||
(defun lsp-go-install-save-hooks ()
|
||||
(add-hook 'before-save-hook #'lsp-format-buffer t t)
|
||||
(add-hook 'before-save-hook #'lsp-organize-imports t t))
|
||||
(add-hook 'go-mode-hook #'lsp-go-install-save-hooks)
|
||||
```
|
||||
|
||||
### Configuring `gopls` via LSP Mode
|
||||
|
||||
See [settings] for information about available gopls settings.
|
||||
|
||||
Stable gopls settings have corresponding configuration variables in `lsp-mode`.
|
||||
For example, `(setq lsp-gopls-use-placeholders nil)` will disable placeholders
|
||||
in completion snippets. See [`lsp-go`] for a list of available variables.
|
||||
|
||||
Experimental settings can be configured via `lsp-register-custom-settings`:
|
||||
|
||||
```lisp
|
||||
(lsp-register-custom-settings
|
||||
'(("gopls.completeUnimported" t t)
|
||||
("gopls.staticcheck" t t)))
|
||||
```
|
||||
|
||||
Note that after changing settings you must restart gopls using e.g. `M-x
|
||||
lsp-restart-workspace`.
|
||||
|
||||
## Configuring Eglot
|
||||
|
||||
### Configuring `project` for Go modules in `.emacs`
|
||||
|
||||
Eglot uses the built-in `project` package to identify the LSP workspace for a
|
||||
newly-opened buffer. The `project` package does not natively know about `GOPATH`
|
||||
or Go modules. Fortunately, you can give it a custom hook to tell it to look for
|
||||
the nearest parent `go.mod` file (that is, the root of the Go module) as the
|
||||
project root.
|
||||
|
||||
```elisp
|
||||
(require 'project)
|
||||
|
||||
(defun project-find-go-module (dir)
|
||||
(when-let ((root (locate-dominating-file dir "go.mod")))
|
||||
(cons 'go-module root)))
|
||||
|
||||
(cl-defmethod project-root ((project (head go-module)))
|
||||
(cdr project))
|
||||
|
||||
(add-hook 'project-find-functions #'project-find-go-module)
|
||||
```
|
||||
|
||||
### Loading Eglot in `.emacs`
|
||||
|
||||
```elisp
|
||||
;; Optional: load other packages before eglot to enable eglot integrations.
|
||||
(require 'company)
|
||||
(require 'yasnippet)
|
||||
|
||||
(require 'go-mode)
|
||||
(require 'eglot)
|
||||
(add-hook 'go-mode-hook 'eglot-ensure)
|
||||
|
||||
;; Optional: install eglot-format-buffer as a save hook.
|
||||
;; The depth of -10 places this before eglot's willSave notification,
|
||||
;; so that that notification reports the actual contents that will be saved.
|
||||
(defun eglot-format-buffer-before-save ()
|
||||
(add-hook 'before-save-hook #'eglot-format-buffer -10 t))
|
||||
(add-hook 'go-mode-hook #'eglot-format-buffer-before-save)
|
||||
```
|
||||
|
||||
### Configuring `gopls` via Eglot
|
||||
|
||||
See [settings] for information about available gopls settings.
|
||||
|
||||
LSP server settings are controlled by the `eglot-workspace-configuration`
|
||||
variable, which can be set either globally in `.emacs` or in a `.dir-locals.el` file in the project root.
|
||||
|
||||
`.emacs`:
|
||||
```elisp
|
||||
(setq-default eglot-workspace-configuration
|
||||
'((:gopls .
|
||||
((staticcheck . t)
|
||||
(matcher . "CaseSensitive")))))
|
||||
```
|
||||
|
||||
`.dir-locals.el`:
|
||||
```elisp
|
||||
((nil (eglot-workspace-configuration . ((gopls . ((staticcheck . t)
|
||||
(matcher . "CaseSensitive")))))))
|
||||
```
|
||||
|
||||
### Organizing imports with Eglot
|
||||
|
||||
`gopls` provides the import-organizing functionality of `goimports` as an LSP
|
||||
code action, which you can invoke as needed by running `M-x eglot-code-actions`
|
||||
(or a key of your choice bound to the `eglot-code-actions` function) and
|
||||
selecting `Organize Imports` at the prompt.
|
||||
|
||||
To automatically organize imports before saving, add a hook:
|
||||
|
||||
```elisp
|
||||
(add-hook 'before-save-hook
|
||||
(lambda ()
|
||||
(call-interactively 'eglot-code-action-organize-imports))
|
||||
nil t)
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Common errors:
|
||||
|
||||
* When prompted by Emacs for your project folder, if you are using modules you
|
||||
must select the module's root folder (i.e. the directory with the "go.mod").
|
||||
If you are using GOPATH, select your $GOPATH as your folder.
|
||||
* Emacs must have your environment set properly (PATH, GOPATH, etc). You can
|
||||
run `M-x getenv <RET> PATH <RET>` to see if your PATH is set in Emacs. If
|
||||
not, you can try starting Emacs from your terminal, using [this
|
||||
package][exec-path-from-shell], or moving your shell config from `.bashrc`
|
||||
into `.profile` and logging out and back in.
|
||||
* Make sure only one LSP client mode is installed. (For example, if using
|
||||
`lsp-mode`, ensure that you are not _also_ enabling `eglot`.)
|
||||
* Look for errors in the `*lsp-log*` buffer or run `M-x eglot-events-buffer`.
|
||||
* Ask for help in the `#emacs` channel on the [Gophers slack].
|
||||
|
||||
[LSP Mode]: https://emacs-lsp.github.io/lsp-mode/
|
||||
[Eglot]: https://github.com/joaotavora/eglot/blob/master/README.md
|
||||
[`xref`]: https://www.gnu.org/software/emacs/manual/html_node/emacs/Xref.html
|
||||
[Flymake]: https://www.gnu.org/software/emacs/manual/html_node/flymake/Using-Flymake.html#Using-Flymake
|
||||
[Company]: https://company-mode.github.io/
|
||||
[`completion-at-point`]: https://www.gnu.org/software/emacs/manual/html_node/elisp/Completion-in-Buffers.html
|
||||
[ElDoc]: https://elpa.gnu.org/packages/eldoc.html
|
||||
[`lsp-ui`]: https://emacs-lsp.github.io/lsp-ui/
|
||||
[`lsp-go`]: https://github.com/emacs-lsp/lsp-mode/blob/master/clients/lsp-go.el
|
||||
[`use-package`]: https://github.com/jwiegley/use-package
|
||||
[`exec-path-from-shell`]: https://github.com/purcell/exec-path-from-shell
|
||||
[settings]: settings.md
|
||||
[Gophers slack]: https://invite.slack.golangbridge.org/
|
||||
@@ -0,0 +1,55 @@
|
||||
# Features
|
||||
|
||||
This document describes some of the features supported by `gopls`. It is
|
||||
currently under construction, so, for a comprehensive list, see the
|
||||
[Language Server Protocol](https://microsoft.github.io/language-server-protocol/).
|
||||
|
||||
## Special features
|
||||
|
||||
Here, only special features outside of the LSP are described.
|
||||
|
||||
### Symbol Queries
|
||||
|
||||
Gopls supports some extended syntax for `workspace/symbol` requests, when using
|
||||
the `fuzzy` symbol matcher (the default). Inspired by the popular fuzzy matcher
|
||||
[FZF](https://github.com/junegunn/fzf), the following special characters are
|
||||
supported within symbol queries:
|
||||
|
||||
| Character | Usage | Match |
|
||||
| --------- | --------- | ------------ |
|
||||
| `'` | `'abc` | exact |
|
||||
| `^` | `^printf` | exact prefix |
|
||||
| `$` | `printf$` | exact suffix |
|
||||
|
||||
## Template Files
|
||||
|
||||
Gopls provides some support for Go template files, that is, files that
|
||||
are parsed by `text/template` or `html/template`.
|
||||
Gopls recognizes template files based on their file extension, which may be
|
||||
configured by the
|
||||
[`templateExtensions`](https://github.com/golang/tools/blob/master/gopls/doc/settings.md#templateextensions) setting.
|
||||
Making this list empty turns off template support.
|
||||
|
||||
In template files, template support works inside
|
||||
the default `{{` delimiters. (Go template parsing
|
||||
allows the user to specify other delimiters, but
|
||||
gopls does not know how to do that.)
|
||||
|
||||
Gopls template support includes the following features:
|
||||
+ **Diagnostics**: if template parsing returns an error,
|
||||
it is presented as a diagnostic. (Missing functions do not produce errors.)
|
||||
+ **Syntax Highlighting**: syntax highlighting is provided for template files.
|
||||
+ **Definitions**: gopls provides jump-to-definition inside templates, though it does not understand scoping (all templates are considered to be in one global scope).
|
||||
+ **References**: gopls provides find-references, with the same scoping limitation as definitions.
|
||||
+ **Completions**: gopls will attempt to suggest completions inside templates.
|
||||
|
||||
### Configuring your editor
|
||||
|
||||
In addition to configuring `templateExtensions`, you may need to configure your
|
||||
editor or LSP client to activate `gopls` for template files. For example, in
|
||||
`VS Code` you will need to configure both
|
||||
[`files.associations`](https://code.visualstudio.com/docs/languages/identifiers)
|
||||
and `build.templateExtensions` (the gopls setting).
|
||||
|
||||
<!--TODO(rstambler): Automatically generate a list of supported features.-->
|
||||
|
||||
@@ -0,0 +1,917 @@
|
||||
// Copyright 2024 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.
|
||||
|
||||
// The generate command updates the following files of documentation:
|
||||
//
|
||||
// gopls/doc/settings.md -- from linking gopls/internal/settings.DefaultOptions
|
||||
// gopls/doc/commands.md -- from loading gopls/internal/protocol/command
|
||||
// gopls/doc/analyzers.md -- from linking gopls/internal/settings.DefaultAnalyzers
|
||||
// gopls/doc/inlayHints.md -- from linking gopls/internal/golang.AllInlayHints
|
||||
// gopls/internal/doc/api.json -- all of the above in a single value, for 'gopls api-json'
|
||||
//
|
||||
// Run it with this command:
|
||||
//
|
||||
// $ cd gopls/doc && go generate
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/tools/gopls/internal/cache"
|
||||
"golang.org/x/tools/gopls/internal/doc"
|
||||
"golang.org/x/tools/gopls/internal/golang"
|
||||
"golang.org/x/tools/gopls/internal/mod"
|
||||
"golang.org/x/tools/gopls/internal/protocol/command/commandmeta"
|
||||
"golang.org/x/tools/gopls/internal/settings"
|
||||
"golang.org/x/tools/gopls/internal/util/maps"
|
||||
"golang.org/x/tools/gopls/internal/util/safetoken"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if _, err := doMain(true); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Generation failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// doMain regenerates the output files. On success:
|
||||
// - if write, it updates them;
|
||||
// - if !write, it reports whether they would change.
|
||||
func doMain(write bool) (bool, error) {
|
||||
// TODO(adonovan): when we can rely on go1.23,
|
||||
// switch to gotypesalias=1 behavior.
|
||||
//
|
||||
// (Since this program is run by 'go run',
|
||||
// the gopls/go.mod file's go 1.19 directive doesn't
|
||||
// have its usual effect of setting gotypesalias=0.)
|
||||
os.Setenv("GODEBUG", "gotypesalias=0")
|
||||
|
||||
api, err := loadAPI()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
goplsDir, err := pkgDir("golang.org/x/tools/gopls")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// TODO(adonovan): consider using HTML, not Markdown, for the
|
||||
// generated reference documents. It's not more difficult, the
|
||||
// layout is easier to read, and we can use go/doc-comment
|
||||
// rendering logic.
|
||||
|
||||
for _, f := range []struct {
|
||||
name string // relative to gopls
|
||||
rewrite rewriter
|
||||
}{
|
||||
{"internal/doc/api.json", rewriteAPI},
|
||||
{"doc/settings.md", rewriteSettings},
|
||||
{"doc/codelenses.md", rewriteCodeLenses},
|
||||
{"doc/commands.md", rewriteCommands},
|
||||
{"doc/analyzers.md", rewriteAnalyzers},
|
||||
{"doc/inlayHints.md", rewriteInlayHints},
|
||||
} {
|
||||
file := filepath.Join(goplsDir, f.name)
|
||||
old, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
new, err := f.rewrite(old, api)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("rewriting %q: %v", file, err)
|
||||
}
|
||||
|
||||
if write {
|
||||
if err := os.WriteFile(file, new, 0); err != nil {
|
||||
return false, err
|
||||
}
|
||||
} else if !bytes.Equal(old, new) {
|
||||
return false, nil // files would change
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// A rewriter is a function that transforms the content of a file.
|
||||
type rewriter = func([]byte, *doc.API) ([]byte, error)
|
||||
|
||||
// pkgDir returns the directory corresponding to the import path pkgPath.
|
||||
func pkgDir(pkgPath string) (string, error) {
|
||||
cmd := exec.Command("go", "list", "-f", "{{.Dir}}", pkgPath)
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
if ee, _ := err.(*exec.ExitError); ee != nil && len(ee.Stderr) > 0 {
|
||||
return "", fmt.Errorf("%v: %w\n%s", cmd, err, ee.Stderr)
|
||||
}
|
||||
return "", fmt.Errorf("%v: %w", cmd, err)
|
||||
}
|
||||
return strings.TrimSpace(string(out)), nil
|
||||
}
|
||||
|
||||
// loadAPI computes the JSON-encodable value that describes gopls'
|
||||
// interfaces, by a combination of static and dynamic analysis.
|
||||
func loadAPI() (*doc.API, error) {
|
||||
pkgs, err := packages.Load(
|
||||
&packages.Config{
|
||||
Mode: packages.NeedTypes | packages.NeedTypesInfo | packages.NeedSyntax | packages.NeedDeps,
|
||||
},
|
||||
"golang.org/x/tools/gopls/internal/settings",
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
settingsPkg := pkgs[0]
|
||||
|
||||
defaults := settings.DefaultOptions()
|
||||
api := &doc.API{
|
||||
Options: map[string][]*doc.Option{},
|
||||
Analyzers: loadAnalyzers(settings.DefaultAnalyzers), // no staticcheck analyzers
|
||||
}
|
||||
|
||||
api.Commands, err = loadCommands()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
api.Lenses, err = loadLenses(settingsPkg, defaults.Codelenses)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
api.Hints = loadHints(golang.AllInlayHints)
|
||||
for _, category := range []reflect.Value{
|
||||
reflect.ValueOf(defaults.UserOptions),
|
||||
} {
|
||||
// Find the type information and ast.File corresponding to the category.
|
||||
optsType := settingsPkg.Types.Scope().Lookup(category.Type().Name())
|
||||
if optsType == nil {
|
||||
return nil, fmt.Errorf("could not find %v in scope %v", category.Type().Name(), settingsPkg.Types.Scope())
|
||||
}
|
||||
opts, err := loadOptions(category, optsType, settingsPkg, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
catName := strings.TrimSuffix(category.Type().Name(), "Options")
|
||||
api.Options[catName] = opts
|
||||
|
||||
// Hardcode the expected values for the "analyses" and "hints" settings,
|
||||
// since their map keys are strings, not enums.
|
||||
for _, opt := range opts {
|
||||
switch opt.Name {
|
||||
case "analyses":
|
||||
for _, a := range api.Analyzers {
|
||||
opt.EnumKeys.Keys = append(opt.EnumKeys.Keys, doc.EnumKey{
|
||||
Name: fmt.Sprintf("%q", a.Name),
|
||||
Doc: a.Doc,
|
||||
Default: strconv.FormatBool(a.Default),
|
||||
})
|
||||
}
|
||||
case "hints":
|
||||
// TODO(adonovan): simplify InlayHints to use an enum,
|
||||
// following CodeLensSource.
|
||||
for _, a := range api.Hints {
|
||||
opt.EnumKeys.Keys = append(opt.EnumKeys.Keys, doc.EnumKey{
|
||||
Name: fmt.Sprintf("%q", a.Name),
|
||||
Doc: a.Doc,
|
||||
Default: strconv.FormatBool(a.Default),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return api, nil
|
||||
}
|
||||
|
||||
// loadOptions computes a single category of settings by a combination
|
||||
// of static analysis and reflection over gopls internal types.
|
||||
func loadOptions(category reflect.Value, optsType types.Object, pkg *packages.Package, hierarchy string) ([]*doc.Option, error) {
|
||||
file, err := fileForPos(pkg, optsType.Pos())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
enums, err := loadEnums(pkg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var opts []*doc.Option
|
||||
optsStruct := optsType.Type().Underlying().(*types.Struct)
|
||||
for i := 0; i < optsStruct.NumFields(); i++ {
|
||||
// The types field gives us the type.
|
||||
typesField := optsStruct.Field(i)
|
||||
|
||||
// If the field name ends with "Options", assume it is a struct with
|
||||
// additional options and process it recursively.
|
||||
if h := strings.TrimSuffix(typesField.Name(), "Options"); h != typesField.Name() {
|
||||
// Keep track of the parent structs.
|
||||
if hierarchy != "" {
|
||||
h = hierarchy + "." + h
|
||||
}
|
||||
options, err := loadOptions(category, typesField, pkg, strings.ToLower(h))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts = append(opts, options...)
|
||||
continue
|
||||
}
|
||||
path, _ := astutil.PathEnclosingInterval(file, typesField.Pos(), typesField.Pos())
|
||||
if len(path) < 2 {
|
||||
return nil, fmt.Errorf("could not find AST node for field %v", typesField)
|
||||
}
|
||||
// The AST field gives us the doc.
|
||||
astField, ok := path[1].(*ast.Field)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected AST path %v", path)
|
||||
}
|
||||
|
||||
// The reflect field gives us the default value.
|
||||
reflectField := category.FieldByName(typesField.Name())
|
||||
if !reflectField.IsValid() {
|
||||
return nil, fmt.Errorf("could not find reflect field for %v", typesField.Name())
|
||||
}
|
||||
|
||||
def, err := formatDefault(reflectField)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Derive the doc-and-api.json type from the Go field type.
|
||||
//
|
||||
// In principle, we should use JSON nomenclature here
|
||||
// (number, array, object, etc; see #68057), but in
|
||||
// practice we use the Go type string ([]T, map[K]V,
|
||||
// etc) with only one tweak: enumeration types are
|
||||
// replaced by "enum", including when they appear as
|
||||
// map keys.
|
||||
//
|
||||
// Notable edge cases:
|
||||
// - any (e.g. in linksInHover) is really a sum of false | true | "internal".
|
||||
// - time.Duration is really a string with a particular syntax.
|
||||
typ := typesField.Type().String()
|
||||
if _, ok := enums[typesField.Type()]; ok {
|
||||
typ = "enum"
|
||||
}
|
||||
name := lowerFirst(typesField.Name())
|
||||
|
||||
// enum-keyed maps
|
||||
var enumKeys doc.EnumKeys
|
||||
if m, ok := typesField.Type().Underlying().(*types.Map); ok {
|
||||
values, ok := enums[m.Key()]
|
||||
if ok {
|
||||
// Update type name: "map[CodeLensSource]T" -> "map[enum]T"
|
||||
// hack: assumes key substring is unique!
|
||||
typ = strings.Replace(typ, m.Key().String(), "enum", 1)
|
||||
}
|
||||
|
||||
// Edge case: "analyses" is a string (not enum) keyed map,
|
||||
// but its EnumKeys.ValueType was historically populated.
|
||||
// (But not "env"; not sure why.)
|
||||
if ok || name == "analyses" {
|
||||
enumKeys.ValueType = m.Elem().String()
|
||||
|
||||
// For map[enum]T fields, gather the set of valid
|
||||
// EnumKeys (from type information). If T=bool, also
|
||||
// record the default value (from reflection).
|
||||
keys, err := collectEnumKeys(m, reflectField, values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
enumKeys.Keys = keys
|
||||
}
|
||||
}
|
||||
|
||||
// Get the status of the field by checking its struct tags.
|
||||
reflectStructField, ok := category.Type().FieldByName(typesField.Name())
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no struct field for %s", typesField.Name())
|
||||
}
|
||||
status := reflectStructField.Tag.Get("status")
|
||||
|
||||
opts = append(opts, &doc.Option{
|
||||
Name: name,
|
||||
Type: typ,
|
||||
Doc: lowerFirst(astField.Doc.Text()),
|
||||
Default: def,
|
||||
EnumKeys: enumKeys,
|
||||
EnumValues: enums[typesField.Type()],
|
||||
Status: status,
|
||||
Hierarchy: hierarchy,
|
||||
})
|
||||
}
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
// loadEnums returns a description of gopls' settings enum types based on static analysis.
|
||||
func loadEnums(pkg *packages.Package) (map[types.Type][]doc.EnumValue, error) {
|
||||
enums := map[types.Type][]doc.EnumValue{}
|
||||
for _, name := range pkg.Types.Scope().Names() {
|
||||
obj := pkg.Types.Scope().Lookup(name)
|
||||
cnst, ok := obj.(*types.Const)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
f, err := fileForPos(pkg, cnst.Pos())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("finding file for %q: %v", cnst.Name(), err)
|
||||
}
|
||||
path, _ := astutil.PathEnclosingInterval(f, cnst.Pos(), cnst.Pos())
|
||||
spec := path[1].(*ast.ValueSpec)
|
||||
value := cnst.Val().ExactString()
|
||||
docstring := valueDoc(cnst.Name(), value, spec.Doc.Text())
|
||||
v := doc.EnumValue{
|
||||
Value: value,
|
||||
Doc: docstring,
|
||||
}
|
||||
enums[obj.Type()] = append(enums[obj.Type()], v)
|
||||
}
|
||||
return enums, nil
|
||||
}
|
||||
|
||||
func collectEnumKeys(m *types.Map, reflectField reflect.Value, enumValues []doc.EnumValue) ([]doc.EnumKey, error) {
|
||||
// We can get default values for enum -> bool maps.
|
||||
var isEnumBoolMap bool
|
||||
if basic, ok := m.Elem().Underlying().(*types.Basic); ok && basic.Kind() == types.Bool {
|
||||
isEnumBoolMap = true
|
||||
}
|
||||
var keys []doc.EnumKey
|
||||
for _, v := range enumValues {
|
||||
var def string
|
||||
if isEnumBoolMap {
|
||||
var err error
|
||||
def, err = formatDefaultFromEnumBoolMap(reflectField, v.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
keys = append(keys, doc.EnumKey{
|
||||
Name: v.Value,
|
||||
Doc: v.Doc,
|
||||
Default: def,
|
||||
})
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func formatDefaultFromEnumBoolMap(reflectMap reflect.Value, enumKey string) (string, error) {
|
||||
if reflectMap.Kind() != reflect.Map {
|
||||
return "", nil
|
||||
}
|
||||
name := enumKey
|
||||
if unquoted, err := strconv.Unquote(name); err == nil {
|
||||
name = unquoted
|
||||
}
|
||||
for _, e := range reflectMap.MapKeys() {
|
||||
if e.String() == name {
|
||||
value := reflectMap.MapIndex(e)
|
||||
if value.Type().Kind() == reflect.Bool {
|
||||
return formatDefault(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Assume that if the value isn't mentioned in the map, it defaults to
|
||||
// the default value, false.
|
||||
return formatDefault(reflect.ValueOf(false))
|
||||
}
|
||||
|
||||
// formatDefault formats the default value into a JSON-like string.
|
||||
// VS Code exposes settings as JSON, so showing them as JSON is reasonable.
|
||||
// TODO(rstambler): Reconsider this approach, as the VS Code Go generator now
|
||||
// marshals to JSON.
|
||||
func formatDefault(reflectField reflect.Value) (string, error) {
|
||||
def := reflectField.Interface()
|
||||
|
||||
// Durations marshal as nanoseconds, but we want the stringy versions,
|
||||
// e.g. "100ms".
|
||||
if t, ok := def.(time.Duration); ok {
|
||||
def = t.String()
|
||||
}
|
||||
defBytes, err := json.Marshal(def)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Nil values format as "null" so print them as hardcoded empty values.
|
||||
switch reflectField.Type().Kind() {
|
||||
case reflect.Map:
|
||||
if reflectField.IsNil() {
|
||||
defBytes = []byte("{}")
|
||||
}
|
||||
case reflect.Slice:
|
||||
if reflectField.IsNil() {
|
||||
defBytes = []byte("[]")
|
||||
}
|
||||
}
|
||||
return string(defBytes), err
|
||||
}
|
||||
|
||||
// valueDoc transforms a docstring documenting an constant identifier to a
|
||||
// docstring documenting its value.
|
||||
//
|
||||
// If doc is of the form "Foo is a bar", it returns '`"fooValue"` is a bar'. If
|
||||
// doc is non-standard ("this value is a bar"), it returns '`"fooValue"`: this
|
||||
// value is a bar'.
|
||||
func valueDoc(name, value, doc string) string {
|
||||
if doc == "" {
|
||||
return ""
|
||||
}
|
||||
if strings.HasPrefix(doc, name) {
|
||||
// docstring in standard form. Replace the subject with value.
|
||||
return fmt.Sprintf("`%s`%s", value, doc[len(name):])
|
||||
}
|
||||
return fmt.Sprintf("`%s`: %s", value, doc)
|
||||
}
|
||||
|
||||
func loadCommands() ([]*doc.Command, error) {
|
||||
var commands []*doc.Command
|
||||
|
||||
cmds, err := commandmeta.Load()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Parse the objects it contains.
|
||||
for _, cmd := range cmds {
|
||||
cmdjson := &doc.Command{
|
||||
Command: cmd.Name,
|
||||
Title: cmd.Title,
|
||||
Doc: cmd.Doc,
|
||||
ArgDoc: argsDoc(cmd.Args),
|
||||
}
|
||||
if cmd.Result != nil {
|
||||
cmdjson.ResultDoc = typeDoc(cmd.Result, 0)
|
||||
}
|
||||
commands = append(commands, cmdjson)
|
||||
}
|
||||
return commands, nil
|
||||
}
|
||||
|
||||
func argsDoc(args []*commandmeta.Field) string {
|
||||
var b strings.Builder
|
||||
for i, arg := range args {
|
||||
b.WriteString(typeDoc(arg, 0))
|
||||
if i != len(args)-1 {
|
||||
b.WriteString(",\n")
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func typeDoc(arg *commandmeta.Field, level int) string {
|
||||
// Max level to expand struct fields.
|
||||
const maxLevel = 3
|
||||
if len(arg.Fields) > 0 {
|
||||
if level < maxLevel {
|
||||
return arg.FieldMod + structDoc(arg.Fields, level)
|
||||
}
|
||||
return "{ ... }"
|
||||
}
|
||||
under := arg.Type.Underlying()
|
||||
switch u := under.(type) {
|
||||
case *types.Slice:
|
||||
return fmt.Sprintf("[]%s", u.Elem().Underlying().String())
|
||||
}
|
||||
// TODO(adonovan): use (*types.Package).Name qualifier.
|
||||
return types.TypeString(under, nil)
|
||||
}
|
||||
|
||||
// TODO(adonovan): this format is strange; it's not Go, nor JSON, nor LSP. Rethink.
|
||||
func structDoc(fields []*commandmeta.Field, level int) string {
|
||||
var b strings.Builder
|
||||
b.WriteString("{\n")
|
||||
indent := strings.Repeat("\t", level)
|
||||
for _, fld := range fields {
|
||||
if fld.Doc != "" && level == 0 {
|
||||
doclines := strings.Split(fld.Doc, "\n")
|
||||
for _, line := range doclines {
|
||||
text := ""
|
||||
if line != "" {
|
||||
text = " " + line
|
||||
}
|
||||
fmt.Fprintf(&b, "%s\t//%s\n", indent, text)
|
||||
}
|
||||
}
|
||||
tag := strings.Split(fld.JSONTag, ",")[0]
|
||||
if tag == "" {
|
||||
tag = fld.Name
|
||||
}
|
||||
fmt.Fprintf(&b, "%s\t%q: %s,\n", indent, tag, typeDoc(fld, level+1))
|
||||
}
|
||||
fmt.Fprintf(&b, "%s}", indent)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// loadLenses combines the syntactic comments from the settings
|
||||
// package with the default values from settings.DefaultOptions(), and
|
||||
// returns a list of Code Lens descriptors.
|
||||
func loadLenses(settingsPkg *packages.Package, defaults map[settings.CodeLensSource]bool) ([]*doc.Lens, error) {
|
||||
// Find the CodeLensSource enums among the files of the protocol package.
|
||||
// Map each enum value to its doc comment.
|
||||
enumDoc := make(map[string]string)
|
||||
for _, f := range settingsPkg.Syntax {
|
||||
for _, decl := range f.Decls {
|
||||
if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.CONST {
|
||||
for _, spec := range decl.Specs {
|
||||
spec := spec.(*ast.ValueSpec)
|
||||
posn := safetoken.StartPosition(settingsPkg.Fset, spec.Pos())
|
||||
if id, ok := spec.Type.(*ast.Ident); ok && id.Name == "CodeLensSource" {
|
||||
if len(spec.Names) != 1 || len(spec.Values) != 1 {
|
||||
return nil, fmt.Errorf("%s: declare one CodeLensSource per line", posn)
|
||||
}
|
||||
lit, ok := spec.Values[0].(*ast.BasicLit)
|
||||
if !ok && lit.Kind != token.STRING {
|
||||
return nil, fmt.Errorf("%s: CodeLensSource value is not a string literal", posn)
|
||||
}
|
||||
value, _ := strconv.Unquote(lit.Value) // ignore error: AST is well-formed
|
||||
if spec.Doc == nil {
|
||||
return nil, fmt.Errorf("%s: %s lacks doc comment", posn, spec.Names[0].Name)
|
||||
}
|
||||
enumDoc[value] = spec.Doc.Text()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(enumDoc) == 0 {
|
||||
return nil, fmt.Errorf("failed to extract any CodeLensSource declarations")
|
||||
}
|
||||
|
||||
// Build list of Lens descriptors.
|
||||
var lenses []*doc.Lens
|
||||
addAll := func(sources map[settings.CodeLensSource]cache.CodeLensSourceFunc, fileType string) error {
|
||||
slice := maps.Keys(sources)
|
||||
sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] })
|
||||
for _, source := range slice {
|
||||
docText, ok := enumDoc[string(source)]
|
||||
if !ok {
|
||||
return fmt.Errorf("missing CodeLensSource declaration for %s", source)
|
||||
}
|
||||
title, docText, _ := strings.Cut(docText, "\n") // first line is title
|
||||
lenses = append(lenses, &doc.Lens{
|
||||
FileType: fileType,
|
||||
Lens: string(source),
|
||||
Title: title,
|
||||
Doc: docText,
|
||||
Default: defaults[source],
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
addAll(golang.CodeLensSources(), "Go")
|
||||
addAll(mod.CodeLensSources(), "go.mod")
|
||||
return lenses, nil
|
||||
}
|
||||
|
||||
func loadAnalyzers(m map[string]*settings.Analyzer) []*doc.Analyzer {
|
||||
var sorted []string
|
||||
for _, a := range m {
|
||||
sorted = append(sorted, a.Analyzer().Name)
|
||||
}
|
||||
sort.Strings(sorted)
|
||||
var json []*doc.Analyzer
|
||||
for _, name := range sorted {
|
||||
a := m[name]
|
||||
json = append(json, &doc.Analyzer{
|
||||
Name: a.Analyzer().Name,
|
||||
Doc: a.Analyzer().Doc,
|
||||
URL: a.Analyzer().URL,
|
||||
Default: a.EnabledByDefault(),
|
||||
})
|
||||
}
|
||||
return json
|
||||
}
|
||||
|
||||
func loadHints(m map[string]*golang.Hint) []*doc.Hint {
|
||||
var sorted []string
|
||||
for _, h := range m {
|
||||
sorted = append(sorted, h.Name)
|
||||
}
|
||||
sort.Strings(sorted)
|
||||
var json []*doc.Hint
|
||||
for _, name := range sorted {
|
||||
h := m[name]
|
||||
json = append(json, &doc.Hint{
|
||||
Name: h.Name,
|
||||
Doc: h.Doc,
|
||||
})
|
||||
}
|
||||
return json
|
||||
}
|
||||
|
||||
func lowerFirst(x string) string {
|
||||
if x == "" {
|
||||
return x
|
||||
}
|
||||
return strings.ToLower(x[:1]) + x[1:]
|
||||
}
|
||||
|
||||
func upperFirst(x string) string {
|
||||
if x == "" {
|
||||
return x
|
||||
}
|
||||
return strings.ToUpper(x[:1]) + x[1:]
|
||||
}
|
||||
|
||||
func fileForPos(pkg *packages.Package, pos token.Pos) (*ast.File, error) {
|
||||
fset := pkg.Fset
|
||||
for _, f := range pkg.Syntax {
|
||||
if safetoken.StartPosition(fset, f.Pos()).Filename == safetoken.StartPosition(fset, pos).Filename {
|
||||
return f, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("no file for pos %v", pos)
|
||||
}
|
||||
|
||||
func rewriteAPI(_ []byte, api *doc.API) ([]byte, error) {
|
||||
return json.MarshalIndent(api, "", "\t")
|
||||
}
|
||||
|
||||
type optionsGroup struct {
|
||||
title string // dotted path (e.g. "ui.documentation")
|
||||
final string // final segment of title (e.g. "documentation")
|
||||
level int
|
||||
options []*doc.Option
|
||||
}
|
||||
|
||||
func rewriteSettings(prevContent []byte, api *doc.API) ([]byte, error) {
|
||||
content := prevContent
|
||||
for category, opts := range api.Options {
|
||||
groups := collectGroups(opts)
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
// First, print a table of contents (ToC).
|
||||
fmt.Fprintln(&buf)
|
||||
for _, h := range groups {
|
||||
title := h.final
|
||||
if title != "" {
|
||||
fmt.Fprintf(&buf, "%s* [%s](#%s)\n",
|
||||
strings.Repeat(" ", h.level),
|
||||
capitalize(title),
|
||||
strings.ToLower(title))
|
||||
}
|
||||
}
|
||||
|
||||
// Section titles are h2, options are h3.
|
||||
// This is independent of the option hierarchy.
|
||||
// (Nested options should not be smaller!)
|
||||
fmt.Fprintln(&buf)
|
||||
for _, h := range groups {
|
||||
title := h.final
|
||||
if title != "" {
|
||||
// Emit HTML anchor as GitHub markdown doesn't support
|
||||
// "# Heading {#anchor}" syntax.
|
||||
fmt.Fprintf(&buf, "<a id='%s'></a>\n", strings.ToLower(title))
|
||||
|
||||
fmt.Fprintf(&buf, "## %s\n\n", capitalize(title))
|
||||
}
|
||||
for _, opt := range h.options {
|
||||
// Emit HTML anchor as GitHub markdown doesn't support
|
||||
// "# Heading {#anchor}" syntax.
|
||||
//
|
||||
// (Each option name is the camelCased name of a field of
|
||||
// settings.UserOptions or one of its FooOptions subfields.)
|
||||
fmt.Fprintf(&buf, "<a id='%s'></a>\n", opt.Name)
|
||||
|
||||
// heading
|
||||
//
|
||||
// TODO(adonovan): We should display not the Go type (e.g.
|
||||
// `time.Duration`, `map[Enum]bool`) for each setting,
|
||||
// but its JSON type, since that's the actual interface.
|
||||
// We need a better way to derive accurate JSON type descriptions
|
||||
// from Go types. eg. "a string parsed as if by
|
||||
// `time.Duration.Parse`". (`time.Duration` is an integer, not
|
||||
// a string!)
|
||||
//
|
||||
// We do not display the undocumented dotted-path alias
|
||||
// (h.title + "." + opt.Name) used by VS Code only.
|
||||
fmt.Fprintf(&buf, "### `%s` *%v*\n\n", opt.Name, opt.Type)
|
||||
|
||||
// status
|
||||
switch opt.Status {
|
||||
case "":
|
||||
case "advanced":
|
||||
fmt.Fprint(&buf, "**This is an advanced setting and should not be configured by most `gopls` users.**\n\n")
|
||||
case "debug":
|
||||
fmt.Fprint(&buf, "**This setting is for debugging purposes only.**\n\n")
|
||||
case "experimental":
|
||||
fmt.Fprint(&buf, "**This setting is experimental and may be deleted.**\n\n")
|
||||
default:
|
||||
fmt.Fprintf(&buf, "**Status: %s.**\n\n", opt.Status)
|
||||
}
|
||||
|
||||
// doc comment
|
||||
buf.WriteString(opt.Doc)
|
||||
|
||||
// enums
|
||||
write := func(name, doc string) {
|
||||
if doc != "" {
|
||||
unbroken := parBreakRE.ReplaceAllString(doc, "\\\n")
|
||||
fmt.Fprintf(&buf, "* %s\n", strings.TrimSpace(unbroken))
|
||||
} else {
|
||||
fmt.Fprintf(&buf, "* `%s`\n", name)
|
||||
}
|
||||
}
|
||||
if len(opt.EnumValues) > 0 && opt.Type == "enum" {
|
||||
// enum as top-level type constructor
|
||||
buf.WriteString("\nMust be one of:\n\n")
|
||||
for _, val := range opt.EnumValues {
|
||||
write(val.Value, val.Doc)
|
||||
}
|
||||
} else if len(opt.EnumKeys.Keys) > 0 && shouldShowEnumKeysInSettings(opt.Name) {
|
||||
// enum as map key (currently just "annotations")
|
||||
buf.WriteString("\nEach enum must be one of:\n\n")
|
||||
for _, val := range opt.EnumKeys.Keys {
|
||||
write(val.Name, val.Doc)
|
||||
}
|
||||
}
|
||||
|
||||
// default value
|
||||
fmt.Fprintf(&buf, "\nDefault: `%v`.\n\n", opt.Default)
|
||||
}
|
||||
}
|
||||
newContent, err := replaceSection(content, category, buf.Bytes())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
content = newContent
|
||||
}
|
||||
return content, nil
|
||||
}
|
||||
|
||||
var parBreakRE = regexp.MustCompile("\n{2,}")
|
||||
|
||||
func shouldShowEnumKeysInSettings(name string) bool {
|
||||
// These fields have too many possible options,
|
||||
// or too voluminous documentation, to render as enums.
|
||||
// Instead they each get their own page in the manual.
|
||||
return !(name == "analyses" || name == "codelenses" || name == "hints")
|
||||
}
|
||||
|
||||
func collectGroups(opts []*doc.Option) []optionsGroup {
|
||||
optsByHierarchy := map[string][]*doc.Option{}
|
||||
for _, opt := range opts {
|
||||
optsByHierarchy[opt.Hierarchy] = append(optsByHierarchy[opt.Hierarchy], opt)
|
||||
}
|
||||
|
||||
// As a hack, assume that uncategorized items are less important to
|
||||
// users and force the empty string to the end of the list.
|
||||
var containsEmpty bool
|
||||
var sorted []string
|
||||
for h := range optsByHierarchy {
|
||||
if h == "" {
|
||||
containsEmpty = true
|
||||
continue
|
||||
}
|
||||
sorted = append(sorted, h)
|
||||
}
|
||||
sort.Strings(sorted)
|
||||
if containsEmpty {
|
||||
sorted = append(sorted, "")
|
||||
}
|
||||
var groups []optionsGroup
|
||||
baseLevel := 0
|
||||
for _, h := range sorted {
|
||||
split := strings.SplitAfter(h, ".")
|
||||
last := split[len(split)-1]
|
||||
// Hack to capitalize all of UI.
|
||||
if last == "ui" {
|
||||
last = "UI"
|
||||
}
|
||||
// A hierarchy may look like "ui.formatting". If "ui" has no
|
||||
// options of its own, it may not be added to the map, but it
|
||||
// still needs a heading.
|
||||
components := strings.Split(h, ".")
|
||||
for i := 1; i < len(components); i++ {
|
||||
parent := strings.Join(components[0:i], ".")
|
||||
if _, ok := optsByHierarchy[parent]; !ok {
|
||||
groups = append(groups, optionsGroup{
|
||||
title: parent,
|
||||
final: last,
|
||||
level: baseLevel + i,
|
||||
})
|
||||
}
|
||||
}
|
||||
groups = append(groups, optionsGroup{
|
||||
title: h,
|
||||
final: last,
|
||||
level: baseLevel + strings.Count(h, "."),
|
||||
options: optsByHierarchy[h],
|
||||
})
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
func capitalize(s string) string {
|
||||
return string(unicode.ToUpper(rune(s[0]))) + s[1:]
|
||||
}
|
||||
|
||||
func rewriteCodeLenses(prevContent []byte, api *doc.API) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
for _, lens := range api.Lenses {
|
||||
fmt.Fprintf(&buf, "## `%s`: %s\n\n", lens.Lens, lens.Title)
|
||||
fmt.Fprintf(&buf, "%s\n\n", lens.Doc)
|
||||
fmt.Fprintf(&buf, "Default: %v\n\n", onOff(lens.Default))
|
||||
fmt.Fprintf(&buf, "File type: %s\n\n", lens.FileType)
|
||||
}
|
||||
return replaceSection(prevContent, "Lenses", buf.Bytes())
|
||||
}
|
||||
|
||||
func rewriteCommands(prevContent []byte, api *doc.API) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
for _, command := range api.Commands {
|
||||
fmt.Fprintf(&buf, "## `%s`: **%s**\n\n%v\n\n", command.Command, command.Title, command.Doc)
|
||||
if command.ArgDoc != "" {
|
||||
fmt.Fprintf(&buf, "Args:\n\n```\n%s\n```\n\n", command.ArgDoc)
|
||||
}
|
||||
if command.ResultDoc != "" {
|
||||
fmt.Fprintf(&buf, "Result:\n\n```\n%s\n```\n\n", command.ResultDoc)
|
||||
}
|
||||
}
|
||||
return replaceSection(prevContent, "Commands", buf.Bytes())
|
||||
}
|
||||
|
||||
func rewriteAnalyzers(prevContent []byte, api *doc.API) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
for _, analyzer := range api.Analyzers {
|
||||
fmt.Fprintf(&buf, "<a id='%s'></a>\n", analyzer.Name)
|
||||
title, doc, _ := strings.Cut(analyzer.Doc, "\n")
|
||||
title = strings.TrimPrefix(title, analyzer.Name+": ")
|
||||
fmt.Fprintf(&buf, "## `%s`: %s\n\n", analyzer.Name, title)
|
||||
fmt.Fprintf(&buf, "%s\n\n", doc)
|
||||
fmt.Fprintf(&buf, "Default: %s.", onOff(analyzer.Default))
|
||||
if !analyzer.Default {
|
||||
fmt.Fprintf(&buf, " Enable by setting `\"analyses\": {\"%s\": true}`.", analyzer.Name)
|
||||
}
|
||||
fmt.Fprintf(&buf, "\n\n")
|
||||
if analyzer.URL != "" {
|
||||
// TODO(adonovan): currently the URL provides the same information
|
||||
// as 'doc' above, though that may change due to
|
||||
// https://github.com/golang/go/issues/61315#issuecomment-1841350181.
|
||||
// In that case, update this to something like "Complete documentation".
|
||||
fmt.Fprintf(&buf, "Package documentation: [%s](%s)\n\n",
|
||||
analyzer.Name, analyzer.URL)
|
||||
}
|
||||
|
||||
}
|
||||
return replaceSection(prevContent, "Analyzers", buf.Bytes())
|
||||
}
|
||||
|
||||
func rewriteInlayHints(prevContent []byte, api *doc.API) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
for _, hint := range api.Hints {
|
||||
fmt.Fprintf(&buf, "## **%v**\n\n", hint.Name)
|
||||
fmt.Fprintf(&buf, "%s\n\n", hint.Doc)
|
||||
switch hint.Default {
|
||||
case true:
|
||||
fmt.Fprintf(&buf, "**Enabled by default.**\n\n")
|
||||
case false:
|
||||
fmt.Fprintf(&buf, "**Disabled by default. Enable it by setting `\"hints\": {\"%s\": true}`.**\n\n", hint.Name)
|
||||
}
|
||||
}
|
||||
return replaceSection(prevContent, "Hints", buf.Bytes())
|
||||
}
|
||||
|
||||
// replaceSection replaces the portion of a file delimited by comments of the form:
|
||||
//
|
||||
// <!-- BEGIN sectionName -->
|
||||
// <!-- END section Name -->
|
||||
func replaceSection(content []byte, sectionName string, replacement []byte) ([]byte, error) {
|
||||
re := regexp.MustCompile(fmt.Sprintf(`(?s)<!-- BEGIN %v.* -->\n(.*?)<!-- END %v.* -->`, sectionName, sectionName))
|
||||
idx := re.FindSubmatchIndex(content)
|
||||
if idx == nil {
|
||||
return nil, fmt.Errorf("could not find section %q", sectionName)
|
||||
}
|
||||
result := append([]byte(nil), content[:idx[2]]...)
|
||||
result = append(result, replacement...)
|
||||
result = append(result, content[idx[3]:]...)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type onOff bool
|
||||
|
||||
func (o onOff) String() string {
|
||||
if o {
|
||||
return "on"
|
||||
} else {
|
||||
return "off"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2020 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 main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/internal/testenv"
|
||||
)
|
||||
|
||||
func TestGenerated(t *testing.T) {
|
||||
testenv.NeedsGoPackages(t)
|
||||
// This test fails on Kokoro, for unknown reasons, so must be run only on TryBots.
|
||||
// In any case, it suffices to run this test on any builder.
|
||||
testenv.NeedsGo1Point(t, 21)
|
||||
|
||||
testenv.NeedsLocalXTools(t)
|
||||
|
||||
ok, err := doMain(false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !ok {
|
||||
t.Error("documentation needs updating. Run: cd gopls && go generate ./...")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
# Helix
|
||||
|
||||
Configuring `gopls` to work with Helix is rather straightforward. Install `gopls`, and then add it to the `PATH` variable. If it is in the `PATH` variable, Helix will be able to detect it automatically.
|
||||
|
||||
The documentation explaining how to install the default language servers for Helix can be found [here](https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers)
|
||||
|
||||
## Installing `gopls`
|
||||
|
||||
The first step is to install `gopls` on your machine.
|
||||
You can follow installation instructions [here](https://github.com/golang/tools/tree/master/gopls#installation).
|
||||
|
||||
## Setting your path to include `gopls`
|
||||
|
||||
Set your `PATH` environment variable to point to `gopls`.
|
||||
If you used `go install` to download `gopls`, it should be in `$GOPATH/bin`.
|
||||
If you don't have `GOPATH` set, you can use `go env GOPATH` to find it.
|
||||
|
||||
## Additional information
|
||||
|
||||
You can find more information about how to set up the LSP formatter [here](https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers#autoformatting).
|
||||
|
||||
It is possible to use `hx --health go` to see that the language server is properly set up.
|
||||
|
||||
### Configuration
|
||||
|
||||
The settings for `gopls` can be configured in the `languages.toml` file.
|
||||
The official Helix documentation for this can be found [here](https://docs.helix-editor.com/languages.html)
|
||||
|
||||
Configuration pertaining to `gopls` should be in the table `language-server.gopls`.
|
||||
|
||||
#### How to set flags
|
||||
|
||||
To set flags, add them to the `args` array in the `language-server.gopls` section of the `languages.toml` file.
|
||||
|
||||
#### How to set LSP configuration
|
||||
|
||||
Configuration options can be set in the `language-server.gopls.config` section of the `languages.toml` file, or in the `config` key of the `language-server.gopls` section of the `languages.toml` file.
|
||||
|
||||
#### A minimal config example
|
||||
|
||||
In the `~/.config/helix/languages.toml` file, the following snippet would set up `gopls` with a logfile located at `/tmp/gopls.log` and enable staticcheck.
|
||||
|
||||
```toml
|
||||
[language-server.gopls]
|
||||
command = "gopls"
|
||||
args = ["-logfile=/tmp/gopls.log", "serve"]
|
||||
[language-server.gopls.config]
|
||||
"ui.diagnostic.staticcheck" = true
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
# Hints
|
||||
|
||||
This document describes the inlay hints that `gopls` uses inside the editor.
|
||||
|
||||
<!-- BEGIN Hints: DO NOT MANUALLY EDIT THIS SECTION -->
|
||||
## **assignVariableTypes**
|
||||
|
||||
Enable/disable inlay hints for variable types in assign statements:
|
||||
```go
|
||||
i/* int*/, j/* int*/ := 0, len(r)-1
|
||||
```
|
||||
|
||||
**Disabled by default. Enable it by setting `"hints": {"assignVariableTypes": true}`.**
|
||||
|
||||
## **compositeLiteralFields**
|
||||
|
||||
Enable/disable inlay hints for composite literal field names:
|
||||
```go
|
||||
{/*in: */"Hello, world", /*want: */"dlrow ,olleH"}
|
||||
```
|
||||
|
||||
**Disabled by default. Enable it by setting `"hints": {"compositeLiteralFields": true}`.**
|
||||
|
||||
## **compositeLiteralTypes**
|
||||
|
||||
Enable/disable inlay hints for composite literal types:
|
||||
```go
|
||||
for _, c := range []struct {
|
||||
in, want string
|
||||
}{
|
||||
/*struct{ in string; want string }*/{"Hello, world", "dlrow ,olleH"},
|
||||
}
|
||||
```
|
||||
|
||||
**Disabled by default. Enable it by setting `"hints": {"compositeLiteralTypes": true}`.**
|
||||
|
||||
## **constantValues**
|
||||
|
||||
Enable/disable inlay hints for constant values:
|
||||
```go
|
||||
const (
|
||||
KindNone Kind = iota/* = 0*/
|
||||
KindPrint/* = 1*/
|
||||
KindPrintf/* = 2*/
|
||||
KindErrorf/* = 3*/
|
||||
)
|
||||
```
|
||||
|
||||
**Disabled by default. Enable it by setting `"hints": {"constantValues": true}`.**
|
||||
|
||||
## **functionTypeParameters**
|
||||
|
||||
Enable/disable inlay hints for implicit type parameters on generic functions:
|
||||
```go
|
||||
myFoo/*[int, string]*/(1, "hello")
|
||||
```
|
||||
|
||||
**Disabled by default. Enable it by setting `"hints": {"functionTypeParameters": true}`.**
|
||||
|
||||
## **parameterNames**
|
||||
|
||||
Enable/disable inlay hints for parameter names:
|
||||
```go
|
||||
parseInt(/* str: */ "123", /* radix: */ 8)
|
||||
```
|
||||
|
||||
**Disabled by default. Enable it by setting `"hints": {"parameterNames": true}`.**
|
||||
|
||||
## **rangeVariableTypes**
|
||||
|
||||
Enable/disable inlay hints for variable types in range statements:
|
||||
```go
|
||||
for k/* int*/, v/* string*/ := range []string{} {
|
||||
fmt.Println(k, v)
|
||||
}
|
||||
```
|
||||
|
||||
**Disabled by default. Enable it by setting `"hints": {"rangeVariableTypes": true}`.**
|
||||
|
||||
<!-- END Hints: DO NOT MANUALLY EDIT THIS SECTION -->
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
@@ -0,0 +1,161 @@
|
||||
|
||||
Gopls v0.14 supports a new refactoring operation:
|
||||
inlining of function calls.
|
||||
|
||||
You can find it in VS Code by selecting a static call to a function or
|
||||
method f and choosing the `Refactor...` command followed by `Inline
|
||||
call to f`.
|
||||
Other editors and LSP clients have their own idiomatic command for it;
|
||||
for example, in Emacs with Eglot it is
|
||||
[`M-x eglot-code-action-inline`](https://joaotavora.github.io/eglot/#index-M_002dx-eglot_002dcode_002daction_002dinline)
|
||||
and in Vim with coc.nvim it is `coc-rename`.
|
||||
|
||||
<!-- source code used for images:
|
||||
|
||||
func six() int {
|
||||
return sum(1, 2, 3)
|
||||
}
|
||||
|
||||
func sum(values ...int) int {
|
||||
total := 0
|
||||
for _, v := range values {
|
||||
total += v
|
||||
}
|
||||
return total
|
||||
}
|
||||
-->
|
||||

|
||||

|
||||
|
||||
Inlining replaces the call expression by a copy of the function body,
|
||||
with parameters replaced by arguments.
|
||||
Inlining is useful for a number of reasons.
|
||||
Perhaps you want to eliminate a call to a deprecated
|
||||
function such as `ioutil.ReadFile` by replacing it with a call to the
|
||||
newer `os.ReadFile`; inlining will do that for you.
|
||||
Or perhaps you want to copy and modify an existing function in some
|
||||
way; inlining can provide a starting point.
|
||||
The inlining logic also provides a building block for
|
||||
other refactorings to come, such as "change signature".
|
||||
|
||||
Not every call can be inlined.
|
||||
Of course, the tool needs to know which function is being called, so
|
||||
you can't inline a dynamic call through a function value or interface
|
||||
method; but static calls to methods are fine.
|
||||
Nor can you inline a call if the callee is declared in another package
|
||||
and refers to non-exported parts of that package, or to [internal
|
||||
packages](https://go.dev/doc/go1.4#internalpackages) that are
|
||||
inaccessible to the caller.
|
||||
|
||||
When inlining is possible, it's critical that the tool preserve
|
||||
the original behavior of the program.
|
||||
We don't want refactoring to break the build, or, worse, to introduce
|
||||
subtle latent bugs.
|
||||
This is especially important when inlining tools are used to perform
|
||||
automated clean-ups in large code bases.
|
||||
We must be able to trust the tool.
|
||||
Our inliner is very careful not to make guesses or unsound
|
||||
assumptions about the behavior of the code.
|
||||
However, that does mean it sometimes produces a change that differs
|
||||
from what someone with expert knowledge of the same code might have
|
||||
written by hand.
|
||||
|
||||
In the most difficult cases, especially with complex control flow, it
|
||||
may not be safe to eliminate the function call at all.
|
||||
For example, the behavior of a `defer` statement is intimately tied to
|
||||
its enclosing function call, and `defer` is the only control
|
||||
construct that can be used to handle panics, so it cannot be reduced
|
||||
into simpler constructs.
|
||||
So, for example, given a function f defined as:
|
||||
|
||||
```go
|
||||
func f(s string) {
|
||||
defer fmt.Println("goodbye")
|
||||
fmt.Println(s)
|
||||
}
|
||||
```
|
||||
a call `f("hello")` will be inlined to:
|
||||
```go
|
||||
func() {
|
||||
defer fmt.Println("goodbye")
|
||||
fmt.Println("hello")
|
||||
}()
|
||||
```
|
||||
Although the parameter was eliminated, the function call remains.
|
||||
|
||||
An inliner is a bit like an optimizing compiler.
|
||||
A compiler is considered "correct" if it doesn't change the meaning of
|
||||
the program in translation from source language to target language.
|
||||
An _optimizing_ compiler exploits the particulars of the input to
|
||||
generate better code, where "better" usually means more efficient.
|
||||
As users report inputs that cause the compiler to emit suboptimal
|
||||
code, the compiler is improved to recognize more cases, or more rules,
|
||||
and more exceptions to rules---but this process has no end.
|
||||
Inlining is similar, except that "better" code means tidier code.
|
||||
The most conservative translation provides a simple but (hopefully!)
|
||||
correct foundation, on top of which endless rules, and exceptions to
|
||||
rules, can embellish and improve the quality of the output.
|
||||
|
||||
The following section lists some of the technical
|
||||
challenges involved in sound inlining:
|
||||
|
||||
- **Effects:** When replacing a parameter by its argument expression,
|
||||
we must be careful not to change the effects of the call. For
|
||||
example, if we call a function `func twice(x int) int { return x + x }`
|
||||
with `twice(g())`, we do not want to see `g() + g()`, which would
|
||||
cause g's effects to occur twice, and potentially each call might
|
||||
return a different value. All effects must occur the same number of
|
||||
times, and in the same order. This requires analyzing both the
|
||||
arguments and the callee function to determine whether they are
|
||||
"pure", whether they read variables, or whether (and when) they
|
||||
update them too. The inliner will introduce a declaration such as
|
||||
`var x int = g()` when it cannot prove that it is safe to substitute
|
||||
the argument throughout.
|
||||
|
||||
- **Constants:** If inlining always replaced a parameter by its argument
|
||||
when the value is constant, some programs would no longer build
|
||||
because checks previously done at run time would happen at compile time.
|
||||
For example `func index(s string, i int) byte { return s[i] }`
|
||||
is a valid function, but if inlining were to replace the call `index("abc", 3)`
|
||||
by the expression `"abc"[3]`, the compiler will report that the
|
||||
index `3` is out of bounds for the string `"abc"`.
|
||||
The inliner will prevent substitution of parameters by problematic
|
||||
constant arguments, again introducing a `var` declaration instead.
|
||||
|
||||
- **Referential integrity:** When a parameter variable is replaced by
|
||||
its argument expression, we must ensure that any names in the
|
||||
argument expression continue to refer to the same thing---not to a
|
||||
different declaration in the callee function body that happens to
|
||||
use the same name! The inliner must replace local references such as
|
||||
`Printf` by qualified references such as `fmt.Printf`, and add an
|
||||
import of package `fmt` as needed.
|
||||
|
||||
- **Implicit conversions:** When passing an argument to a function, it
|
||||
is implicitly converted to the parameter type.
|
||||
If we eliminate the parameter variable, we don't want to
|
||||
lose the conversion as it may be important.
|
||||
For example, in `func f(x any) { y := x; fmt.Printf("%T", &y) }` the
|
||||
type of variable y is `any`, so the program prints `"*interface{}"`.
|
||||
But if inlining the call `f(1)` were to produce the statement `y :=
|
||||
1`, then the type of y would have changed to `int`, which could
|
||||
cause a compile error or, as in this case, a bug, as the program
|
||||
now prints `"*int"`. When the inliner substitutes a parameter variable
|
||||
by its argument value, it may need to introduce explicit conversions
|
||||
of each value to the original parameter type, such as `y := any(1)`.
|
||||
|
||||
- **Last reference:** When an argument expression has no effects
|
||||
and its corresponding parameter is never used, the expression
|
||||
may be eliminated. However, if the expression contains the last
|
||||
reference to a local variable at the caller, this may cause a compile
|
||||
error because the variable is now unused! So the inliner must be
|
||||
cautious about eliminating references to local variables.
|
||||
|
||||
This is just a taste of the problem domain. If you're curious, the
|
||||
documentation for [golang.org/x/tools/internal/refactor/inline](https://pkg.go.dev/golang.org/x/tools/internal/refactor/inline) has
|
||||
more detail. All of this is to say, it's a complex problem, and we aim
|
||||
for correctness first of all. We've already implemented a number of
|
||||
important "tidiness optimizations" and we expect more to follow.
|
||||
|
||||
Please give the inliner a try, and if you find any bugs (where the
|
||||
transformation is incorrect), please do report them. We'd also like to
|
||||
hear what "optimizations" you'd like to see next.
|
||||
@@ -0,0 +1,10 @@
|
||||
This directory contains the draft release notes for each upcoming release.
|
||||
|
||||
Be sure to update the file for the forthcoming release in the same CL
|
||||
that you add new features or fix noteworthy bugs.
|
||||
|
||||
See https://github.com/golang/tools/releases for all past releases.
|
||||
|
||||
Tip: when reviewing edits to markdown files in Gerrit, to see the
|
||||
rendered form, click the "Open in Code Search" link (magnifying glass
|
||||
in blue square) then click "View in > gitiles" (shortcut: `v g`).
|
||||
@@ -0,0 +1,270 @@
|
||||
# gopls/v0.16.0
|
||||
|
||||
<!-- TODO: update this instruction once v0.16.0 is released -->
|
||||
```
|
||||
go install golang.org/x/tools/gopls@v0.16.0-pre.1
|
||||
```
|
||||
|
||||
This release includes several features and bug fixes, and is the first
|
||||
version of gopls to support Go 1.23. To install it, run:
|
||||
|
||||
## New support policy; end of support for Go 1.19 and Go 1.20
|
||||
|
||||
**TL;DR: We are narrowing gopls' support window, but this is unlikely to
|
||||
affect you as long as you use at least Go 1.21 to build gopls. This doesn't
|
||||
affect gopls' support for the code you are writing.**
|
||||
|
||||
This is the last release of gopls that may be built with Go 1.19 or Go 1.20,
|
||||
and also the last to support integrating with go command versions 1.19 and
|
||||
1.20. If built or used with either of these Go versions, it will display
|
||||
a message advising the user to upgrade.
|
||||
|
||||
When using gopls, there are three versions to be aware of:
|
||||
1. The _gopls build go version_: the version of Go used to build gopls.
|
||||
2. The _go command version_: the version of the go list command executed by
|
||||
gopls to load information about your workspace.
|
||||
3. The _language version_: the version in the go directive of the current
|
||||
file's enclosing go.mod file, which determines the file's Go language
|
||||
semantics.
|
||||
|
||||
This gopls release, v0.16.0, is the final release to support Go 1.19 and Go
|
||||
1.20 as the _gopls build go version_ or _go command version_. There is no
|
||||
change to gopls' support for all _language versions_--in fact this support has
|
||||
somewhat improved with the addition of the `stdversion` analyzer (see below).
|
||||
|
||||
Starting with gopls@v0.17.0, which will be released after Go 1.23.0 is released
|
||||
in August, gopls will only support the latest version of Go as the
|
||||
_gopls build go version_.
|
||||
However, thanks to the [forward compatibility](https://go.dev/blog/toolchain)
|
||||
added to Go 1.21, any necessary toolchain upgrade should be handled
|
||||
automatically for users of Go 1.21 or later, just like any other dependency.
|
||||
Additionally, we are reducing our _go command version_ support window from
|
||||
4 versions to 3. Note that this means if you have at least Go 1.21 installed on
|
||||
your system, you should still be able to `go install` and use gopls@v0.17.0.
|
||||
|
||||
We have no plans to ever change our _language version_ support: we expect that
|
||||
gopls will always support developing programs that target _any_ Go version.
|
||||
|
||||
By focusing on building gopls with the latest Go version, we can significantly
|
||||
reduce our maintenance burden and help improve the stability of future gopls
|
||||
releases. See the newly updated
|
||||
[support policy](https://github.com/golang/tools/tree/master/gopls#support-policy)
|
||||
for details. Please comment on [issue #65917](https://go.dev/issue/65917) if
|
||||
you have concerns about this change.
|
||||
|
||||
## Configuration Changes
|
||||
|
||||
- The experimental "allowImplicitNetworkAccess" setting is deprecated (but not
|
||||
yet removed). Please comment on https://go.dev/issue/66861 if you use this
|
||||
setting and would be impacted by its removal.
|
||||
|
||||
## New features
|
||||
|
||||
### Go 1.23 support
|
||||
|
||||
This version of gopls is the first to support new Go 1.23 language features,
|
||||
including
|
||||
[range-over-func](https://go.dev/wiki/RangefuncExperiment) iterators
|
||||
and support for the
|
||||
[`godebug` directive](https://go.dev/ref/mod#go-mod-file-godebug)
|
||||
in go.mod files.
|
||||
|
||||
### Integrated documentation viewer
|
||||
|
||||
Gopls now offers a "Browse package documentation" code action that opens
|
||||
a local web page displaying the generated documentation for the
|
||||
current Go package in a form similar to https://pkg.go.dev.
|
||||
The page will be initially scrolled to the documentation for the
|
||||
declaration containing the cursor.
|
||||
Use this feature to preview the marked-up documentation as you prepare API
|
||||
changes, or to read the documentation for locally edited packages,
|
||||
even ones that have not yet been saved. Reload the page after an edit
|
||||
to see updated documentation.
|
||||
|
||||
TODO: demo in VS Code.
|
||||
|
||||
Clicking on the source-code link associated with a declaration will
|
||||
cause your editor to navigate to the declaration.
|
||||
|
||||
TODO: demo of source linking.
|
||||
|
||||
Editor support:
|
||||
|
||||
- VS Code: use the `Source action > Browse package documentation` menu item.
|
||||
Note: source links navigate the editor but don't yet raise the window yet.
|
||||
Please upvote https://github.com/microsoft/vscode/issues/208093 and
|
||||
https://github.com/microsoft/vscode/issues/207634 (temporarily closed).
|
||||
|
||||
- Emacs: requires eglot v1.17. You may find this `go-doc` function a
|
||||
useful shortcut:
|
||||
|
||||
```lisp
|
||||
(eglot--code-action eglot-code-action-doc "source.doc")
|
||||
|
||||
(defalias 'go-doc #'eglot-code-action-doc
|
||||
"Browse documentation for the current Go package.")
|
||||
```
|
||||
|
||||
- TODO: test in vim, neovim, sublime, helix.
|
||||
|
||||
The `linksInHover` setting now supports a new value, `"gopls"`,
|
||||
that causes documentation links in the the Markdown output
|
||||
of the Hover operation to link to gopls' internal doc viewer.
|
||||
|
||||
|
||||
### Browse free symbols
|
||||
|
||||
Gopls offers another web-based code action, "Browse free symbols",
|
||||
which displays the free symbols referenced by the selected code.
|
||||
|
||||
A symbol is "free" if it is referenced within the selection but
|
||||
declared outside of it. The free symbols that are variables are, in
|
||||
effect, the set of parameters that would be needed if the block were
|
||||
extracted into its own function in the same package.
|
||||
|
||||
Even when you don't intend to extract a block into a new function,
|
||||
this information can help you to tell at a glance what names a block
|
||||
of code depends on.
|
||||
|
||||
Each dotted path of identifiers (such as `file.Name.Pos`) is reported
|
||||
as a separate item, so that you can see which parts of a complex
|
||||
type are actually needed.
|
||||
|
||||
The free symbols of the body of a function may reveal that
|
||||
only a small part (a single field of a struct, say) of one of the
|
||||
function's parameters is used, allowing you to simplify and generalize
|
||||
the function by choosing a different type for that parameter.
|
||||
|
||||
- TODO screenshot
|
||||
|
||||
- VS Code: use the `Source action > Browse free symbols` menu item.
|
||||
|
||||
- Emacs: requires eglot v1.17. You may find this `go-doc` function a
|
||||
useful shortcut:
|
||||
|
||||
```lisp
|
||||
(eglot--code-action eglot-code-action-freesymbols "source.freesymbols")
|
||||
|
||||
(defalias 'go-freesymbols #'eglot-code-action-freesymbols
|
||||
"Browse free symbols referred to by the current selection.")
|
||||
```
|
||||
TODO(dominikh/go-mode.el#436): add both of these to go-mode.el.
|
||||
|
||||
### Browse assembly
|
||||
|
||||
Gopls offers a third web-based code action, "Browse assembly for f",
|
||||
which displays an assembly listing of the function declaration
|
||||
enclosing the selected code, plus any nested functions (function
|
||||
literals, deferred calls).
|
||||
It invokes the compiler to generate the report.
|
||||
Reloading the page will update the report.
|
||||
|
||||
The machine architecture is determined by the "view" or build
|
||||
configuration that gopls selects for the current file.
|
||||
This is usually the same as your machine's GOARCH unless you are
|
||||
working in a file with `go:build` tags for a different architecture.
|
||||
|
||||
- TODO screenshot
|
||||
|
||||
Gopls cannot yet display assembly for generic functions: generic
|
||||
functions are not fully compiled until they are instantiated, but any
|
||||
function declaration enclosing the selection cannot be an instantiated
|
||||
generic function.
|
||||
<!-- Clearly the ideal UX for generic functions is to use the function
|
||||
symbol under the cursor, e.g. Vector[string], rather than the
|
||||
enclosing function; but computing the name of the linker symbol
|
||||
remains a challenge. -->
|
||||
|
||||
### `unusedwrite` analyzer
|
||||
|
||||
The new
|
||||
[unusedwrite](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unusedwrite)
|
||||
analyzer reports assignments, often to fields of structs, that have no
|
||||
effect because, for example, the struct is never used again:
|
||||
|
||||
```go
|
||||
func scheme(host string) string {
|
||||
u := &url.URL{
|
||||
Host: host, // "unused write to field Host" (no need to construct a URL)
|
||||
Scheme: "https:",
|
||||
}
|
||||
return u.Scheme
|
||||
}
|
||||
```
|
||||
|
||||
This is at best an indication that the code is unnecessarily complex
|
||||
(for instance, some dead code could be removed), but often indicates a
|
||||
bug, as in this example:
|
||||
|
||||
```go
|
||||
type S struct { x int }
|
||||
|
||||
func (s S) set(x int) {
|
||||
s.x = x // "unused write to field x" (s should be a *S pointer)
|
||||
}
|
||||
```
|
||||
|
||||
### `stdversion` analyzer
|
||||
|
||||
The new
|
||||
[`stdversion`](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stdversion)
|
||||
analyzer warns about the use of too-new standard library symbols based on the
|
||||
version of the `go` directive in your `go.mod` file. This improves our support
|
||||
for older _language versions_ (see above), even when gopls is built with
|
||||
a recent Go version.
|
||||
|
||||
TODO: add a screenshot demonstrating the new diagnostics
|
||||
|
||||
### Two more vet analyzers
|
||||
|
||||
The [framepointer](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/framepointer)
|
||||
and [sigchanyzer](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/sigchanyzer)
|
||||
analyzers have long been part of go vet's suite,
|
||||
but had been overlooked in previous versions of gopls.
|
||||
|
||||
Henceforth, gopls will always include any analyzers run by vet.
|
||||
|
||||
### Hover shows size/offset info, and struct tags
|
||||
|
||||
TODO: consolidate release notes related to Hover improvements.
|
||||
|
||||
Hovering over the identifier that declares a type or struct field now
|
||||
displays the size information for the type, and the offset information
|
||||
for the field. In addition, it reports the percentage of wasted space
|
||||
due to suboptimal ordering of struct fields, if this figure is 20% or
|
||||
higher. This information may be helpful when making space
|
||||
optimizations to your data structures, or when reading assembly code.
|
||||
|
||||
TODO: example hover image.
|
||||
|
||||
Hovering over a field with struct tags now also includes those tags.
|
||||
|
||||
TODO: example hover image
|
||||
|
||||
### Hover and definition on doc links
|
||||
|
||||
Go 1.19 added support for [links in doc
|
||||
comments](https://go.dev/doc/comment#links), allowing the
|
||||
documentation for one symbol to reference another:
|
||||
|
||||
TODO: turn the code below into a VS Code screenshot of hover.
|
||||
|
||||
```go
|
||||
// Logf logs a message formatted in the manner of [fmt.Printf].
|
||||
func Logf(format string, args ...any)
|
||||
```
|
||||
|
||||
Gopls's Hover and Definition operations now treat these links just
|
||||
like identifiers, so hovering over one will display information about
|
||||
the symbol, and "Go to definition" will navigate to its declaration.
|
||||
Thanks to @rogeryk for contributing this feature.
|
||||
|
||||
|
||||
## Bugs fixed
|
||||
|
||||
## Thank you to our contributors!
|
||||
|
||||
@guodongli-google for the `unusedwrite` analyzer.
|
||||
TODO: they're a xoogler; is there a more current GH account?
|
||||
|
||||
@rogeryk
|
||||
@@ -0,0 +1,25 @@
|
||||
# Gopls release policy
|
||||
|
||||
Gopls releases follow [semver](http://semver.org), with major changes and new
|
||||
features introduced only in new minor versions (i.e. versions of the form
|
||||
`v*.N.0` for some N). Subsequent patch releases contain only cherry-picked
|
||||
fixes or superficial updates.
|
||||
|
||||
In order to align with the
|
||||
[Go release timeline](https://github.com/golang/go/wiki/Go-Release-Cycle#timeline),
|
||||
we aim to release a new minor version of Gopls approximately every three
|
||||
months, with patch releases approximately every month, according to the
|
||||
following table:
|
||||
|
||||
| Month | Version(s) |
|
||||
| ---- | ------- |
|
||||
| Jan | `v*.<N+0>.0` |
|
||||
| Jan-Mar | `v*.<N+0>.*` |
|
||||
| Apr | `v*.<N+1>.0` |
|
||||
| Apr-Jun | `v*.<N+1>.*` |
|
||||
| Jul | `v*.<N+2>.0` |
|
||||
| Jul-Sep | `v*.<N+2>.*` |
|
||||
| Oct | `v*.<N+3>.0` |
|
||||
| Oct-Dec | `v*.<N+3>.*` |
|
||||
|
||||
For more background on this policy, see https://go.dev/issue/55267.
|
||||
@@ -0,0 +1,121 @@
|
||||
# Semantic Tokens
|
||||
|
||||
The [LSP](https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#textDocument_semanticTokens)
|
||||
specifies semantic tokens as a way of telling clients about language-specific
|
||||
properties of pieces of code in a file being edited.
|
||||
|
||||
The client asks for a set of semantic tokens and modifiers. This note describe which ones
|
||||
gopls will return, and under what circumstances. Gopls has no control over how the client
|
||||
converts semantic tokens into colors (or some other visible indication). In vscode it
|
||||
is possible to modify the color a theme uses by setting the `editor.semanticTokenColorCustomizations`
|
||||
object. We provide a little [guidance](#Colors) later.
|
||||
|
||||
There are 22 semantic tokens, with 10 possible modifiers. The protocol allows each semantic
|
||||
token to be used with any of the 1024 subsets of possible modifiers, but most combinations
|
||||
don't make intuitive sense (although `async documentation` has a certain appeal).
|
||||
|
||||
The 22 semantic tokens are `namespace`, `type`, `class`, `enum`, `interface`,
|
||||
`struct`, `typeParameter`, `parameter`, `variable`, `property`, `enumMember`,
|
||||
`event`, `function`, `method`, `macro`, `keyword`, `modifier`, `comment`,
|
||||
`string`, `number`, `regexp`, `operator`.
|
||||
|
||||
The 10 modifiers are `declaration`, `definition`, `readonly`, `static`,
|
||||
`deprecated`, `abstract`, `async`, `modification`, `documentation`, `defaultLibrary`.
|
||||
|
||||
The authoritative lists are in the [specification](https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#semanticTokenTypes)
|
||||
|
||||
For the implementation to work correctly the client and server have to agree on the ordering
|
||||
of the tokens and of the modifiers. Gopls, therefore, will only send tokens and modifiers
|
||||
that the client has asked for. This document says what gopls would send if the client
|
||||
asked for everything. By default, vscode asks for everything.
|
||||
|
||||
Gopls sends 11 token types for `.go` files and 1 for `.*tmpl` files.
|
||||
Nothing is sent for any other kind of file.
|
||||
This all could change. (When Go has generics, gopls will return `typeParameter`.)
|
||||
|
||||
For `.*tmpl` files gopls sends `macro`, and no modifiers, for each `{{`...`}}` scope.
|
||||
|
||||
## Semantic tokens for Go files
|
||||
|
||||
There are two contrasting guiding principles that might be used to decide what to mark
|
||||
with semantic tokens. All clients already do some kind of syntax marking. E.g., vscode
|
||||
uses a TextMate grammar. The minimal principle would send semantic tokens only for those
|
||||
language features that cannot be reliably found without parsing Go and looking at types.
|
||||
The maximal principle would attempt to convey as much as possible about the Go code,
|
||||
using all available parsing and type information.
|
||||
|
||||
There is much to be said for returning minimal information, but the minimal principle is
|
||||
not well-specified. Gopls has no way of knowing what the clients know about the Go program
|
||||
being edited. Even in vscode the TextMate grammars can be more or less elaborate
|
||||
and change over time. (Nonetheless, a minimal implementation would not return `keyword`,
|
||||
`number`, `comment`, or `string`.)
|
||||
|
||||
The maximal position isn't particularly well-specified either. To chose one example, a
|
||||
format string might have formatting codes (`%[4]-3.6f`), escape sequences (`\U00010604`), and regular
|
||||
characters. Should these all be distinguished? One could even imagine distinguishing
|
||||
different runes by their Unicode language assignment, or some other Unicode property, such as
|
||||
being [confusable](http://www.unicode.org/Public/security/10.0.0/confusables.txt).
|
||||
|
||||
Gopls does not come close to either of these principles. Semantic tokens are returned for
|
||||
identifiers, keywords, operators, comments, and literals. (Semantic tokens do not
|
||||
cover the file. They are not returned for
|
||||
white space or punctuation, and there is no semantic token for labels.)
|
||||
The following describes more precisely what gopls
|
||||
does, with a few notes on possible alternative choices.
|
||||
The references to *object* refer to the
|
||||
```types.Object``` returned by the type checker. The references to *nodes* refer to the
|
||||
```ast.Node``` from the parser.
|
||||
|
||||
1. __`keyword`__ All Go [keywords](https://golang.org/ref/spec#Keywords) are marked `keyword`.
|
||||
1. __`namespace`__ All package names are marked `namespace`. In an import, if there is an
|
||||
alias, it would be marked. Otherwise the last component of the import path is marked.
|
||||
1. __`type`__ Objects of type ```types.TypeName``` are marked `type`.
|
||||
If they are also ```types.Basic```
|
||||
the modifier is `defaultLibrary`. (And in ```type B struct{C}```, ```B``` has modifier `definition`.)
|
||||
1. __`parameter`__ The formal arguments in ```ast.FuncDecl``` and ```ast.FuncType``` nodes are marked `parameter`.
|
||||
1. __`variable`__ Identifiers in the
|
||||
scope of ```const``` are modified with `readonly`. ```nil``` is usually a `variable` modified with both
|
||||
`readonly` and `defaultLibrary`. (```nil``` is a predefined identifier; the user can redefine it,
|
||||
in which case it would just be a variable, or whatever.) Identifiers of type ```types.Variable``` are,
|
||||
not surprisingly, marked `variable`. Identifiers being defined (node ```ast.GenDecl```) are modified
|
||||
by `definition` and, if appropriate, `readonly`. Receivers (in method declarations) are
|
||||
`variable`.
|
||||
1. __`method`__ Methods are marked at their definition (```func (x foo) bar() {}```) or declaration
|
||||
in an ```interface```. Methods are not marked where they are used.
|
||||
In ```x.bar()```, ```x``` will be marked
|
||||
either as a `namespace` if it is a package name, or as a `variable` if it is an interface value,
|
||||
so distinguishing ```bar``` seemed superfluous.
|
||||
1. __`function`__ Bultins (```types.Builtin```) are modified with `defaultLibrary`
|
||||
(e.g., ```make```, ```len```, ```copy```). Identifiers whose
|
||||
object is ```types.Func``` or whose node is ```ast.FuncDecl``` are `function`.
|
||||
1. __`comment`__ Comments and struct tags. (Perhaps struct tags should be `property`?)
|
||||
1. __`string`__ Strings. Could add modifiers for e.g., escapes or format codes.
|
||||
1. __`number`__ Numbers. Should the ```i``` in ```23i``` be handled specially?
|
||||
1. __`operator`__ Assignment operators, binary operators, ellipses (```...```), increment/decrement
|
||||
operators, sends (```<-```), and unary operators.
|
||||
|
||||
Gopls will send the modifier `deprecated` if it finds a comment
|
||||
```// deprecated``` in the godoc.
|
||||
|
||||
The unused tokens for Go code are `class`, `enum`, `interface`,
|
||||
`struct`, `typeParameter`, `property`, `enumMember`,
|
||||
`event`, `macro`, `modifier`,
|
||||
`regexp`
|
||||
|
||||
## Colors
|
||||
|
||||
These comments are about vscode.
|
||||
|
||||
The documentation has a [helpful](https://code.visualstudio.com/api/language-extensions/semantic-highlight-guide#custom-textmate-scope-mappings)
|
||||
description of which semantic tokens correspond to scopes in TextMate grammars. Themes seem
|
||||
to use the TextMate scopes to decide on colors.
|
||||
|
||||
Some examples of color customizations are [here](https://medium.com/@danromans/how-to-customize-semantic-token-colorization-with-visual-studio-code-ac3eab96141b).
|
||||
|
||||
## Note
|
||||
|
||||
While a file is being edited it may temporarily contain either
|
||||
parsing errors or type errors. In this case gopls cannot determine some (or maybe any)
|
||||
of the semantic tokens. To avoid weird flickering it is the responsibility
|
||||
of clients to maintain the semantic token information
|
||||
in the unedited part of the file, and they do.
|
||||
@@ -0,0 +1,556 @@
|
||||
# Settings
|
||||
|
||||
This document describes gopls' configuration settings.
|
||||
|
||||
Gopls settings are defined by an JSON object whose valid fields are
|
||||
described below. These fields are gopls-specific, and generic LSP
|
||||
clients have no knowledge of them.
|
||||
|
||||
Different clients present configuration settings in their user
|
||||
interfaces in a wide variety of ways.
|
||||
For example, some expect the user to edit the raw JSON object while
|
||||
others use a data structure in the editor's configuration language;
|
||||
still others (such as VS Code) have a graphical configuration system.
|
||||
Be sure to consult the documentation for how to express configuration
|
||||
settings in your client.
|
||||
Some clients also permit settings to be configured differently for
|
||||
each workspace folder.
|
||||
|
||||
Any settings that are experimental or for debugging purposes are
|
||||
marked as such. To enable all experimental features, use
|
||||
**allExperiments: `true`**. You will still be able to independently
|
||||
override specific experimental features.
|
||||
|
||||
<!--
|
||||
All settings are uniquely identified by name such as `semanticTokens`
|
||||
or `templateExtensions`.
|
||||
However, for convenience of VS Code, each setting also has an
|
||||
undocumented alias whose form is a dotted path such as
|
||||
`ui.semanticTokens` or `build.templateExtensions`.
|
||||
However, only the final segment is actually significant, so
|
||||
`build.templateExtensions` is equivalent to `templateExtensions`.
|
||||
All clients but VS Code should use the short form.
|
||||
-->
|
||||
|
||||
<!-- This portion is generated by doc/generate from the ../internal/settings package. -->
|
||||
<!-- BEGIN User: DO NOT MANUALLY EDIT THIS SECTION -->
|
||||
|
||||
* [Build](#build)
|
||||
* [Formatting](#formatting)
|
||||
* [UI](#ui)
|
||||
* [Completion](#completion)
|
||||
* [Diagnostic](#diagnostic)
|
||||
* [Documentation](#documentation)
|
||||
* [Inlayhint](#inlayhint)
|
||||
* [Navigation](#navigation)
|
||||
|
||||
<a id='build'></a>
|
||||
## Build
|
||||
|
||||
<a id='buildFlags'></a>
|
||||
### `buildFlags` *[]string*
|
||||
|
||||
buildFlags is the set of flags passed on to the build system when invoked.
|
||||
It is applied to queries like `go list`, which is used when discovering files.
|
||||
The most common use is to set `-tags`.
|
||||
|
||||
Default: `[]`.
|
||||
|
||||
<a id='env'></a>
|
||||
### `env` *map[string]string*
|
||||
|
||||
env adds environment variables to external commands run by `gopls`, most notably `go list`.
|
||||
|
||||
Default: `{}`.
|
||||
|
||||
<a id='directoryFilters'></a>
|
||||
### `directoryFilters` *[]string*
|
||||
|
||||
directoryFilters can be used to exclude unwanted directories from the
|
||||
workspace. By default, all directories are included. Filters are an
|
||||
operator, `+` to include and `-` to exclude, followed by a path prefix
|
||||
relative to the workspace folder. They are evaluated in order, and
|
||||
the last filter that applies to a path controls whether it is included.
|
||||
The path prefix can be empty, so an initial `-` excludes everything.
|
||||
|
||||
DirectoryFilters also supports the `**` operator to match 0 or more directories.
|
||||
|
||||
Examples:
|
||||
|
||||
Exclude node_modules at current depth: `-node_modules`
|
||||
|
||||
Exclude node_modules at any depth: `-**/node_modules`
|
||||
|
||||
Include only project_a: `-` (exclude everything), `+project_a`
|
||||
|
||||
Include only project_a, but not node_modules inside it: `-`, `+project_a`, `-project_a/node_modules`
|
||||
|
||||
Default: `["-**/node_modules"]`.
|
||||
|
||||
<a id='templateExtensions'></a>
|
||||
### `templateExtensions` *[]string*
|
||||
|
||||
templateExtensions gives the extensions of file names that are treateed
|
||||
as template files. (The extension
|
||||
is the part of the file name after the final dot.)
|
||||
|
||||
Default: `[]`.
|
||||
|
||||
<a id='memoryMode'></a>
|
||||
### `memoryMode` *string*
|
||||
|
||||
**This setting is experimental and may be deleted.**
|
||||
|
||||
obsolete, no effect
|
||||
|
||||
Default: `""`.
|
||||
|
||||
<a id='expandWorkspaceToModule'></a>
|
||||
### `expandWorkspaceToModule` *bool*
|
||||
|
||||
**This setting is experimental and may be deleted.**
|
||||
|
||||
expandWorkspaceToModule determines which packages are considered
|
||||
"workspace packages" when the workspace is using modules.
|
||||
|
||||
Workspace packages affect the scope of workspace-wide operations. Notably,
|
||||
gopls diagnoses all packages considered to be part of the workspace after
|
||||
every keystroke, so by setting "ExpandWorkspaceToModule" to false, and
|
||||
opening a nested workspace directory, you can reduce the amount of work
|
||||
gopls has to do to keep your workspace up to date.
|
||||
|
||||
Default: `true`.
|
||||
|
||||
<a id='allowImplicitNetworkAccess'></a>
|
||||
### `allowImplicitNetworkAccess` *bool*
|
||||
|
||||
**This setting is experimental and may be deleted.**
|
||||
|
||||
allowImplicitNetworkAccess disables GOPROXY=off, allowing implicit module
|
||||
downloads rather than requiring user action. This option will eventually
|
||||
be removed.
|
||||
|
||||
Default: `false`.
|
||||
|
||||
<a id='standaloneTags'></a>
|
||||
### `standaloneTags` *[]string*
|
||||
|
||||
standaloneTags specifies a set of build constraints that identify
|
||||
individual Go source files that make up the entire main package of an
|
||||
executable.
|
||||
|
||||
A common example of standalone main files is the convention of using the
|
||||
directive `//go:build ignore` to denote files that are not intended to be
|
||||
included in any package, for example because they are invoked directly by
|
||||
the developer using `go run`.
|
||||
|
||||
Gopls considers a file to be a standalone main file if and only if it has
|
||||
package name "main" and has a build directive of the exact form
|
||||
"//go:build tag" or "// +build tag", where tag is among the list of tags
|
||||
configured by this setting. Notably, if the build constraint is more
|
||||
complicated than a simple tag (such as the composite constraint
|
||||
`//go:build tag && go1.18`), the file is not considered to be a standalone
|
||||
main file.
|
||||
|
||||
This setting is only supported when gopls is built with Go 1.16 or later.
|
||||
|
||||
Default: `["ignore"]`.
|
||||
|
||||
<a id='formatting'></a>
|
||||
## Formatting
|
||||
|
||||
<a id='local'></a>
|
||||
### `local` *string*
|
||||
|
||||
local is the equivalent of the `goimports -local` flag, which puts
|
||||
imports beginning with this string after third-party packages. It should
|
||||
be the prefix of the import path whose imports should be grouped
|
||||
separately.
|
||||
|
||||
Default: `""`.
|
||||
|
||||
<a id='gofumpt'></a>
|
||||
### `gofumpt` *bool*
|
||||
|
||||
gofumpt indicates if we should run gofumpt formatting.
|
||||
|
||||
Default: `false`.
|
||||
|
||||
<a id='ui'></a>
|
||||
## UI
|
||||
|
||||
<a id='codelenses'></a>
|
||||
### `codelenses` *map[enum]bool*
|
||||
|
||||
codelenses overrides the enabled/disabled state of each of gopls'
|
||||
sources of [Code Lenses](codelenses.md).
|
||||
|
||||
Example Usage:
|
||||
|
||||
```json5
|
||||
"gopls": {
|
||||
...
|
||||
"codelenses": {
|
||||
"generate": false, // Don't show the `go generate` lens.
|
||||
"gc_details": true // Show a code lens toggling the display of gc's choices.
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Default: `{"gc_details":false,"generate":true,"regenerate_cgo":true,"run_govulncheck":false,"tidy":true,"upgrade_dependency":true,"vendor":true}`.
|
||||
|
||||
<a id='semanticTokens'></a>
|
||||
### `semanticTokens` *bool*
|
||||
|
||||
**This setting is experimental and may be deleted.**
|
||||
|
||||
semanticTokens controls whether the LSP server will send
|
||||
semantic tokens to the client.
|
||||
|
||||
Default: `false`.
|
||||
|
||||
<a id='noSemanticString'></a>
|
||||
### `noSemanticString` *bool*
|
||||
|
||||
**This setting is experimental and may be deleted.**
|
||||
|
||||
noSemanticString turns off the sending of the semantic token 'string'
|
||||
|
||||
Default: `false`.
|
||||
|
||||
<a id='noSemanticNumber'></a>
|
||||
### `noSemanticNumber` *bool*
|
||||
|
||||
**This setting is experimental and may be deleted.**
|
||||
|
||||
noSemanticNumber turns off the sending of the semantic token 'number'
|
||||
|
||||
Default: `false`.
|
||||
|
||||
<a id='completion'></a>
|
||||
## Completion
|
||||
|
||||
<a id='usePlaceholders'></a>
|
||||
### `usePlaceholders` *bool*
|
||||
|
||||
placeholders enables placeholders for function parameters or struct
|
||||
fields in completion responses.
|
||||
|
||||
Default: `false`.
|
||||
|
||||
<a id='completionBudget'></a>
|
||||
### `completionBudget` *time.Duration*
|
||||
|
||||
**This setting is for debugging purposes only.**
|
||||
|
||||
completionBudget is the soft latency goal for completion requests. Most
|
||||
requests finish in a couple milliseconds, but in some cases deep
|
||||
completions can take much longer. As we use up our budget we
|
||||
dynamically reduce the search scope to ensure we return timely
|
||||
results. Zero means unlimited.
|
||||
|
||||
Default: `"100ms"`.
|
||||
|
||||
<a id='matcher'></a>
|
||||
### `matcher` *enum*
|
||||
|
||||
**This is an advanced setting and should not be configured by most `gopls` users.**
|
||||
|
||||
matcher sets the algorithm that is used when calculating completion
|
||||
candidates.
|
||||
|
||||
Must be one of:
|
||||
|
||||
* `"CaseInsensitive"`
|
||||
* `"CaseSensitive"`
|
||||
* `"Fuzzy"`
|
||||
|
||||
Default: `"Fuzzy"`.
|
||||
|
||||
<a id='experimentalPostfixCompletions'></a>
|
||||
### `experimentalPostfixCompletions` *bool*
|
||||
|
||||
**This setting is experimental and may be deleted.**
|
||||
|
||||
experimentalPostfixCompletions enables artificial method snippets
|
||||
such as "someSlice.sort!".
|
||||
|
||||
Default: `true`.
|
||||
|
||||
<a id='completeFunctionCalls'></a>
|
||||
### `completeFunctionCalls` *bool*
|
||||
|
||||
completeFunctionCalls enables function call completion.
|
||||
|
||||
When completing a statement, or when a function return type matches the
|
||||
expected of the expression being completed, completion may suggest call
|
||||
expressions (i.e. may include parentheses).
|
||||
|
||||
Default: `true`.
|
||||
|
||||
<a id='diagnostic'></a>
|
||||
## Diagnostic
|
||||
|
||||
<a id='analyses'></a>
|
||||
### `analyses` *map[string]bool*
|
||||
|
||||
analyses specify analyses that the user would like to enable or disable.
|
||||
A map of the names of analysis passes that should be enabled/disabled.
|
||||
A full list of analyzers that gopls uses can be found in
|
||||
[analyzers.md](https://github.com/golang/tools/blob/master/gopls/doc/analyzers.md).
|
||||
|
||||
Example Usage:
|
||||
|
||||
```json5
|
||||
...
|
||||
"analyses": {
|
||||
"unreachable": false, // Disable the unreachable analyzer.
|
||||
"unusedvariable": true // Enable the unusedvariable analyzer.
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
Default: `{}`.
|
||||
|
||||
<a id='staticcheck'></a>
|
||||
### `staticcheck` *bool*
|
||||
|
||||
**This setting is experimental and may be deleted.**
|
||||
|
||||
staticcheck enables additional analyses from staticcheck.io.
|
||||
These analyses are documented on
|
||||
[Staticcheck's website](https://staticcheck.io/docs/checks/).
|
||||
|
||||
Default: `false`.
|
||||
|
||||
<a id='annotations'></a>
|
||||
### `annotations` *map[enum]bool*
|
||||
|
||||
**This setting is experimental and may be deleted.**
|
||||
|
||||
annotations specifies the various kinds of optimization diagnostics
|
||||
that should be reported by the gc_details command.
|
||||
|
||||
Each enum must be one of:
|
||||
|
||||
* `"bounds"` controls bounds checking diagnostics.
|
||||
* `"escape"` controls diagnostics about escape choices.
|
||||
* `"inline"` controls diagnostics about inlining choices.
|
||||
* `"nil"` controls nil checks.
|
||||
|
||||
Default: `{"bounds":true,"escape":true,"inline":true,"nil":true}`.
|
||||
|
||||
<a id='vulncheck'></a>
|
||||
### `vulncheck` *enum*
|
||||
|
||||
**This setting is experimental and may be deleted.**
|
||||
|
||||
vulncheck enables vulnerability scanning.
|
||||
|
||||
Must be one of:
|
||||
|
||||
* `"Imports"`: In Imports mode, `gopls` will report vulnerabilities that affect packages
|
||||
directly and indirectly used by the analyzed main module.
|
||||
* `"Off"`: Disable vulnerability analysis.
|
||||
|
||||
Default: `"Off"`.
|
||||
|
||||
<a id='diagnosticsDelay'></a>
|
||||
### `diagnosticsDelay` *time.Duration*
|
||||
|
||||
**This is an advanced setting and should not be configured by most `gopls` users.**
|
||||
|
||||
diagnosticsDelay controls the amount of time that gopls waits
|
||||
after the most recent file modification before computing deep diagnostics.
|
||||
Simple diagnostics (parsing and type-checking) are always run immediately
|
||||
on recently modified packages.
|
||||
|
||||
This option must be set to a valid duration string, for example `"250ms"`.
|
||||
|
||||
Default: `"1s"`.
|
||||
|
||||
<a id='diagnosticsTrigger'></a>
|
||||
### `diagnosticsTrigger` *enum*
|
||||
|
||||
**This setting is experimental and may be deleted.**
|
||||
|
||||
diagnosticsTrigger controls when to run diagnostics.
|
||||
|
||||
Must be one of:
|
||||
|
||||
* `"Edit"`: Trigger diagnostics on file edit and save. (default)
|
||||
* `"Save"`: Trigger diagnostics only on file save. Events like initial workspace load
|
||||
or configuration change will still trigger diagnostics.
|
||||
|
||||
Default: `"Edit"`.
|
||||
|
||||
<a id='analysisProgressReporting'></a>
|
||||
### `analysisProgressReporting` *bool*
|
||||
|
||||
analysisProgressReporting controls whether gopls sends progress
|
||||
notifications when construction of its index of analysis facts is taking a
|
||||
long time. Cancelling these notifications will cancel the indexing task,
|
||||
though it will restart after the next change in the workspace.
|
||||
|
||||
When a package is opened for the first time and heavyweight analyses such as
|
||||
staticcheck are enabled, it can take a while to construct the index of
|
||||
analysis facts for all its dependencies. The index is cached in the
|
||||
filesystem, so subsequent analysis should be faster.
|
||||
|
||||
Default: `true`.
|
||||
|
||||
<a id='documentation'></a>
|
||||
## Documentation
|
||||
|
||||
<a id='hoverKind'></a>
|
||||
### `hoverKind` *enum*
|
||||
|
||||
hoverKind controls the information that appears in the hover text.
|
||||
SingleLine and Structured are intended for use only by authors of editor plugins.
|
||||
|
||||
Must be one of:
|
||||
|
||||
* `"FullDocumentation"`
|
||||
* `"NoDocumentation"`
|
||||
* `"SingleLine"`
|
||||
* `"Structured"` is an experimental setting that returns a structured hover format.
|
||||
This format separates the signature from the documentation, so that the client
|
||||
can do more manipulation of these fields.\
|
||||
This should only be used by clients that support this behavior.
|
||||
* `"SynopsisDocumentation"`
|
||||
|
||||
Default: `"FullDocumentation"`.
|
||||
|
||||
<a id='linkTarget'></a>
|
||||
### `linkTarget` *string*
|
||||
|
||||
linkTarget controls where documentation links go.
|
||||
It might be one of:
|
||||
|
||||
* `"godoc.org"`
|
||||
* `"pkg.go.dev"`
|
||||
|
||||
If company chooses to use its own `godoc.org`, its address can be used as well.
|
||||
|
||||
Modules matching the GOPRIVATE environment variable will not have
|
||||
documentation links in hover.
|
||||
|
||||
Default: `"pkg.go.dev"`.
|
||||
|
||||
<a id='linksInHover'></a>
|
||||
### `linksInHover` *any*
|
||||
|
||||
linksInHover controls the presence of documentation links
|
||||
in hover markdown.
|
||||
|
||||
Its legal values are:
|
||||
- `false`, for no links;
|
||||
- `true`, for links to the `linkTarget` domain; or
|
||||
- `"gopls"`, for links to gopls' internal documentation viewer.
|
||||
|
||||
Default: `true`.
|
||||
|
||||
<a id='inlayhint'></a>
|
||||
## Inlayhint
|
||||
|
||||
<a id='hints'></a>
|
||||
### `hints` *map[string]bool*
|
||||
|
||||
**This setting is experimental and may be deleted.**
|
||||
|
||||
hints specify inlay hints that users want to see. A full list of hints
|
||||
that gopls uses can be found in
|
||||
[inlayHints.md](https://github.com/golang/tools/blob/master/gopls/doc/inlayHints.md).
|
||||
|
||||
Default: `{}`.
|
||||
|
||||
<a id='navigation'></a>
|
||||
## Navigation
|
||||
|
||||
<a id='importShortcut'></a>
|
||||
### `importShortcut` *enum*
|
||||
|
||||
importShortcut specifies whether import statements should link to
|
||||
documentation or go to definitions.
|
||||
|
||||
Must be one of:
|
||||
|
||||
* `"Both"`
|
||||
* `"Definition"`
|
||||
* `"Link"`
|
||||
|
||||
Default: `"Both"`.
|
||||
|
||||
<a id='symbolMatcher'></a>
|
||||
### `symbolMatcher` *enum*
|
||||
|
||||
**This is an advanced setting and should not be configured by most `gopls` users.**
|
||||
|
||||
symbolMatcher sets the algorithm that is used when finding workspace symbols.
|
||||
|
||||
Must be one of:
|
||||
|
||||
* `"CaseInsensitive"`
|
||||
* `"CaseSensitive"`
|
||||
* `"FastFuzzy"`
|
||||
* `"Fuzzy"`
|
||||
|
||||
Default: `"FastFuzzy"`.
|
||||
|
||||
<a id='symbolStyle'></a>
|
||||
### `symbolStyle` *enum*
|
||||
|
||||
**This is an advanced setting and should not be configured by most `gopls` users.**
|
||||
|
||||
symbolStyle controls how symbols are qualified in symbol responses.
|
||||
|
||||
Example Usage:
|
||||
|
||||
```json5
|
||||
"gopls": {
|
||||
...
|
||||
"symbolStyle": "Dynamic",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Must be one of:
|
||||
|
||||
* `"Dynamic"` uses whichever qualifier results in the highest scoring
|
||||
match for the given symbol query. Here a "qualifier" is any "/" or "."
|
||||
delimited suffix of the fully qualified symbol. i.e. "to/pkg.Foo.Field" or
|
||||
just "Foo.Field".
|
||||
* `"Full"` is fully qualified symbols, i.e.
|
||||
"path/to/pkg.Foo.Field".
|
||||
* `"Package"` is package qualified symbols i.e.
|
||||
"pkg.Foo.Field".
|
||||
|
||||
Default: `"Dynamic"`.
|
||||
|
||||
<a id='symbolScope'></a>
|
||||
### `symbolScope` *enum*
|
||||
|
||||
symbolScope controls which packages are searched for workspace/symbol
|
||||
requests. When the scope is "workspace", gopls searches only workspace
|
||||
packages. When the scope is "all", gopls searches all loaded packages,
|
||||
including dependencies and the standard library.
|
||||
|
||||
Must be one of:
|
||||
|
||||
* `"all"` matches symbols in any loaded package, including
|
||||
dependencies.
|
||||
* `"workspace"` matches symbols in workspace packages only.
|
||||
|
||||
Default: `"all"`.
|
||||
|
||||
<a id='verboseOutput'></a>
|
||||
### `verboseOutput` *bool*
|
||||
|
||||
**This setting is for debugging purposes only.**
|
||||
|
||||
verboseOutput enables additional debug logging.
|
||||
|
||||
Default: `false`.
|
||||
|
||||
<!-- END User: DO NOT MANUALLY EDIT THIS SECTION -->
|
||||
@@ -0,0 +1,81 @@
|
||||
# Sublime Text
|
||||
|
||||
Use the [LSP] package. After installing it using Package Control, do the following:
|
||||
|
||||
* Open the **Command Palette**
|
||||
* Find and run the command **LSP: Enable Language Server Globally**
|
||||
* Select the **gopls** item. Be careful not to select the similarly named *golsp* by mistake.
|
||||
|
||||
Finally, you should familiarise yourself with the LSP package's *Settings* and *Key Bindings*. Find them under the menu item **Preferences > Package Settings > LSP**.
|
||||
|
||||
## Examples
|
||||
Minimal global LSP settings, that assume **gopls** and **go** appear on the PATH seen by Sublime Text:<br>
|
||||
```
|
||||
{
|
||||
"clients": {
|
||||
"gopls": {
|
||||
"enabled": true,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Global LSP settings that supply a specific PATH for finding **gopls** and **go**, as well as some settings for Sublime LSP itself:
|
||||
```
|
||||
{
|
||||
"clients": {
|
||||
"gopls": {
|
||||
"enabled": true,
|
||||
"env": {
|
||||
"PATH": "/path/to/your/go/bin",
|
||||
}
|
||||
}
|
||||
},
|
||||
// Recommended by https://agniva.me/gopls/2021/01/02/setting-up-gopls-sublime.html
|
||||
// except log_stderr mentioned there is no longer recognized.
|
||||
"show_references_in_quick_panel": true,
|
||||
"log_debug": true,
|
||||
// These two are recommended by LSP-json as replacement for deprecated only_show_lsp_completions
|
||||
"inhibit_snippet_completions": true,
|
||||
"inhibit_word_completions": true,
|
||||
}
|
||||
```
|
||||
|
||||
LSP and gopls settings can also be adjusted on a per-project basis to override global settings.
|
||||
```
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "/path/to/a/folder/one"
|
||||
},
|
||||
{
|
||||
// If you happen to be working on Go itself, this can be helpful; go-dev/bin should be on PATH.
|
||||
"path": "/path/to/your/go-dev/src/cmd"
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"LSP": {
|
||||
"gopls": {
|
||||
// To use a specific version of gopls with Sublime Text LSP (e.g., to try new features in development)
|
||||
"command": [
|
||||
"/path/to/your/go/bin/gopls"
|
||||
],
|
||||
"env": {
|
||||
"PATH": "/path/to/your/go-dev/bin:/path/to/your/go/bin",
|
||||
"GOPATH": "",
|
||||
},
|
||||
"settings": {
|
||||
"experimentalWorkspaceModule": true
|
||||
}
|
||||
}
|
||||
},
|
||||
// This will apply for all languages in this project that have
|
||||
// LSP servers, not just Go, however cannot enable just for Go.
|
||||
"lsp_format_on_save": true,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Usually changes to these settings are recognized after saving the project file, but it may sometimes be necessary to either restart the server(s) (**Tools > LSP > Restart Servers**) or quit and restart Sublime Text itself.
|
||||
|
||||
[LSP]: https://packagecontrol.io/packages/LSP
|
||||
@@ -0,0 +1,48 @@
|
||||
# Troubleshooting
|
||||
|
||||
If you suspect that `gopls` is crashing or not working correctly, please follow the troubleshooting steps below.
|
||||
|
||||
If `gopls` is using too much memory, please follow the steps under [Memory usage](#debug-memory-usage).
|
||||
|
||||
## Steps
|
||||
|
||||
VS Code users should follow [their troubleshooting guide](https://github.com/golang/vscode-go/blob/master/docs/troubleshooting.md), which has more a more specific version of these instructions.
|
||||
|
||||
1. Verify that your project is in good shape by working with it outside of your editor. Running a command like `go build ./...` in the workspace directory will compile everything. For modules, `go mod tidy` is another good check, though it may modify your `go.mod`.
|
||||
1. Check that your editor isn't showing any diagnostics that indicate a problem with your workspace. They may appear as diagnostics on a Go file's package declaration, diagnostics in a go.mod file, or as a status or progress message. Problems in the workspace configuration can cause many different symptoms. See the [workspace setup instructions](workspace.md) for help.
|
||||
1. Make sure `gopls` is up to date by following the [installation instructions](../README.md#installation), then [restarting gopls](#restart-gopls).
|
||||
1. Optionally, [ask for help](#ask-for-help) on Gophers Slack.
|
||||
1. Finally, [report the issue](#file-an-issue) to the `gopls` developers.
|
||||
|
||||
## Restart `gopls`
|
||||
|
||||
`gopls` has no persistent state, so restarting it will fix transient problems. This is good and bad: good, because you can keep working, and bad, because you won't be able to debug the issue until it recurs.
|
||||
|
||||
In most cases, closing all your open editors will guarantee that `gopls` is killed and restarted. If you don't want to do that, there may be an editor command you can use to restart only `gopls`. Note that some `vim` configurations keep the server alive for a while after the editor exits; you may need to explicitly kill `gopls` if you use `vim`.
|
||||
|
||||
## Ask for help
|
||||
|
||||
Gophers Slack has active editor-specific channels like [#emacs](https://gophers.slack.com/archives/C0HKHULEM), [#vim](https://gophers.slack.com/archives/C07GBR52P), and [#vscode](https://gophers.slack.com/archives/C2B4L99RS) that can help debug further. If you're confident the problem is with `gopls`, you can go straight to [#gopls](https://gophers.slack.com/archives/CJZH85XCZ). Invites are [available to everyone](https://invite.slack.golangbridge.org). Come prepared with a short description of the issue, and try to be available to answer questions for a while afterward.
|
||||
|
||||
## File an issue
|
||||
|
||||
We can't diagnose a problem from just a description. When filing an issue, please include as much as possible of the following information:
|
||||
|
||||
1. Your editor and any settings you have configured (for example, your VSCode `settings.json` file).
|
||||
1. A sample program that reproduces the issue, if possible.
|
||||
1. The output of `gopls version` on the command line.
|
||||
1. A complete gopls log file from a session where the issue occurred. It should have a `go env for <workspace folder>` log line near the beginning. It's also helpful to tell us the timestamp the problem occurred, so we can find it the log. See the [instructions](#capture-logs) for information on how to capture gopls logs.
|
||||
|
||||
Your editor may have a command that fills out some of the necessary information, such as `:GoReportGitHubIssue` in `vim-go`. Otherwise, you can use `gopls bug` on the command line. If neither of those work you can start from scratch directly on the [Go issue tracker](https://github.com/golang/go/issues/new?title=x%2Ftools%2Fgopls%3A%20%3Cfill%20this%20in%3E).
|
||||
|
||||
## Capture logs
|
||||
|
||||
You may have to change your editor's configuration to pass a `-logfile` flag to gopls.
|
||||
|
||||
To increase the level of detail in your logs, start `gopls` with the `-rpc.trace` flag. To start a debug server that will allow you to see profiles and memory usage, start `gopls` with `serve --debug=localhost:6060`. You will then be able to view debug information by navigating to `localhost:6060`.
|
||||
|
||||
If you are unsure of how to pass a flag to `gopls` through your editor, please see the [documentation for your editor](../README.md#editors).
|
||||
|
||||
## Debug memory usage
|
||||
|
||||
`gopls` automatically writes out memory debug information when your usage exceeds 1GB. This information can be found in your temporary directory with names like `gopls.1234-5GiB-withnames.zip`. On Windows, your temporary directory will be located at `%TMP%`, and on Unixes, it will be `$TMPDIR`, which is usually `/tmp`. Please [file an issue](#file-an-issue) with this memory debug information attached. If you are uncomfortable sharing the package names of your code, you can share the `-nonames` zip instead, but it's much less useful.
|
||||
@@ -0,0 +1,234 @@
|
||||
# Vim / Neovim
|
||||
|
||||
* [vim-go](#vimgo)
|
||||
* [LanguageClient-neovim](#lcneovim)
|
||||
* [Ale](#ale)
|
||||
* [vim-lsp](#vimlsp)
|
||||
* [vim-lsc](#vimlsc)
|
||||
* [coc.nvim](#cocnvim)
|
||||
* [govim](#govim)
|
||||
* [Neovim v0.5.0+](#neovim)
|
||||
* [Installation](#neovim-install)
|
||||
* [Custom Configuration](#neovim-config)
|
||||
* [Imports](#neovim-imports)
|
||||
* [Omnifunc](#neovim-omnifunc)
|
||||
* [Additional Links](#neovim-links)
|
||||
|
||||
## <a href="#vimgo" id="vimgo">vim-go</a>
|
||||
|
||||
Use [vim-go] ver 1.20+, with the following configuration:
|
||||
|
||||
```vim
|
||||
let g:go_def_mode='gopls'
|
||||
let g:go_info_mode='gopls'
|
||||
```
|
||||
|
||||
## <a href="#lcneovim" id="lcneovim">LanguageClient-neovim</a>
|
||||
|
||||
Use [LanguageClient-neovim], with the following configuration:
|
||||
|
||||
```vim
|
||||
" Launch gopls when Go files are in use
|
||||
let g:LanguageClient_serverCommands = {
|
||||
\ 'go': ['gopls']
|
||||
\ }
|
||||
" Run gofmt on save
|
||||
autocmd BufWritePre *.go :call LanguageClient#textDocument_formatting_sync()
|
||||
```
|
||||
|
||||
## <a href="#ale" id="ale">Ale</a>
|
||||
|
||||
Use [ale]:
|
||||
|
||||
```vim
|
||||
let g:ale_linters = {
|
||||
\ 'go': ['gopls'],
|
||||
\}
|
||||
```
|
||||
|
||||
see [this issue][ale-issue-2179]
|
||||
|
||||
## <a href="#vimlsp" id="vimlsp">vim-lsp</a>
|
||||
|
||||
Use [prabirshrestha/vim-lsp], with the following configuration:
|
||||
|
||||
```vim
|
||||
augroup LspGo
|
||||
au!
|
||||
autocmd User lsp_setup call lsp#register_server({
|
||||
\ 'name': 'go-lang',
|
||||
\ 'cmd': {server_info->['gopls']},
|
||||
\ 'whitelist': ['go'],
|
||||
\ })
|
||||
autocmd FileType go setlocal omnifunc=lsp#complete
|
||||
"autocmd FileType go nmap <buffer> gd <plug>(lsp-definition)
|
||||
"autocmd FileType go nmap <buffer> ,n <plug>(lsp-next-error)
|
||||
"autocmd FileType go nmap <buffer> ,p <plug>(lsp-previous-error)
|
||||
augroup END
|
||||
```
|
||||
|
||||
## <a href="#vimlsc" id="vimlsc">vim-lsc</a>
|
||||
|
||||
Use [natebosch/vim-lsc], with the following configuration:
|
||||
|
||||
```vim
|
||||
let g:lsc_server_commands = {
|
||||
\ "go": {
|
||||
\ "command": "gopls serve",
|
||||
\ "log_level": -1,
|
||||
\ "suppress_stderr": v:true,
|
||||
\ },
|
||||
\}
|
||||
```
|
||||
|
||||
The `log_level` and `suppress_stderr` parts are needed to prevent breakage from logging. See
|
||||
issues [#180](https://github.com/natebosch/vim-lsc/issues/180) and
|
||||
[#213](https://github.com/natebosch/vim-lsc/issues/213).
|
||||
|
||||
## <a href="#cocnvim" id="cocnvim">coc.nvim</a>
|
||||
|
||||
Use [coc.nvim], with the following `coc-settings.json` configuration:
|
||||
|
||||
```json
|
||||
"languageserver": {
|
||||
"go": {
|
||||
"command": "gopls",
|
||||
"rootPatterns": ["go.work", "go.mod", ".vim/", ".git/", ".hg/"],
|
||||
"filetypes": ["go"],
|
||||
"initializationOptions": {
|
||||
"usePlaceholders": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you use `go.work` files, you may want to set the
|
||||
`workspace.workspaceFolderCheckCwd` option. This will force coc.nvim to search
|
||||
parent directories for `go.work` files, even if the current open directory has
|
||||
a `go.mod` file. See the
|
||||
[coc.nvim documentation](https://github.com/neoclide/coc.nvim/wiki/Using-workspaceFolders)
|
||||
for more details.
|
||||
|
||||
Other [settings](settings.md) can be added in `initializationOptions` too.
|
||||
|
||||
The `editor.action.organizeImport` code action will auto-format code and add missing imports. To run this automatically on save, add the following line to your `init.vim`:
|
||||
|
||||
```vim
|
||||
autocmd BufWritePre *.go :call CocAction('runCommand', 'editor.action.organizeImport')
|
||||
```
|
||||
|
||||
## <a href="#govim" id="govim">govim</a>
|
||||
|
||||
In vim classic only, use the experimental [`govim`], simply follow the [install steps][govim-install].
|
||||
|
||||
## <a href="#neovim" id="neovim">Neovim v0.5.0+</a>
|
||||
|
||||
To use the new native LSP client in Neovim, make sure you
|
||||
[install][nvim-install] Neovim v.0.5.0+,
|
||||
the `nvim-lspconfig` configuration helper plugin, and check the
|
||||
[`gopls` configuration section][nvim-lspconfig] there.
|
||||
|
||||
### <a href="#neovim-install" id="neovim-install">Installation</a>
|
||||
|
||||
You can use Neovim's native plugin system. On a Unix system, you can do that by
|
||||
cloning the `nvim-lspconfig` repository into the correct directory:
|
||||
|
||||
```sh
|
||||
dir="${HOME}/.local/share/nvim/site/pack/nvim-lspconfig/opt/nvim-lspconfig/"
|
||||
mkdir -p "$dir"
|
||||
cd "$dir"
|
||||
git clone 'https://github.com/neovim/nvim-lspconfig.git' .
|
||||
```
|
||||
|
||||
### <a href="#neovim-config" id="neovim-config">Configuration</a>
|
||||
|
||||
nvim-lspconfig aims to provide reasonable defaults, so your setup can be very
|
||||
brief.
|
||||
|
||||
```lua
|
||||
local lspconfig = require("lspconfig")
|
||||
lspconfig.gopls.setup({})
|
||||
```
|
||||
|
||||
However, you can also configure `gopls` for your preferences. Here's an
|
||||
example that enables `unusedparams`, `staticcheck`, and `gofumpt`.
|
||||
|
||||
```lua
|
||||
local lspconfig = require("lspconfig")
|
||||
lspconfig.gopls.setup({
|
||||
settings = {
|
||||
gopls = {
|
||||
analyses = {
|
||||
unusedparams = true,
|
||||
},
|
||||
staticcheck = true,
|
||||
gofumpt = true,
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### <a href="#neovim-imports" id="neovim-imports">Imports and Formatting</a>
|
||||
|
||||
Use the following configuration to have your imports organized on save using
|
||||
the logic of `goimports` and your code formatted.
|
||||
|
||||
```lua
|
||||
autocmd("BufWritePre", {
|
||||
pattern = "*.go",
|
||||
callback = function()
|
||||
local params = vim.lsp.util.make_range_params()
|
||||
params.context = {only = {"source.organizeImports"}}
|
||||
-- buf_request_sync defaults to a 1000ms timeout. Depending on your
|
||||
-- machine and codebase, you may want longer. Add an additional
|
||||
-- argument after params if you find that you have to write the file
|
||||
-- twice for changes to be saved.
|
||||
-- E.g., vim.lsp.buf_request_sync(0, "textDocument/codeAction", params, 3000)
|
||||
local result = vim.lsp.buf_request_sync(0, "textDocument/codeAction", params)
|
||||
for cid, res in pairs(result or {}) do
|
||||
for _, r in pairs(res.result or {}) do
|
||||
if r.edit then
|
||||
local enc = (vim.lsp.get_client_by_id(cid) or {}).offset_encoding or "utf-16"
|
||||
vim.lsp.util.apply_workspace_edit(r.edit, enc)
|
||||
end
|
||||
end
|
||||
end
|
||||
vim.lsp.buf.format({async = false})
|
||||
end
|
||||
})
|
||||
```
|
||||
|
||||
### <a href="#neovim-omnifunc" id="neovim-omnifunc">Omnifunc</a>
|
||||
|
||||
In Neovim v0.8.1 and later if you don't set the option `omnifunc`, it will auto
|
||||
set to `v:lua.vim.lsp.omnifunc`. If you are using an earlier version, you can
|
||||
configure it manually:
|
||||
|
||||
```lua
|
||||
local on_attach = function(client, bufnr)
|
||||
-- Enable completion triggered by <c-x><c-o>
|
||||
vim.api.nvim_buf_set_option(bufnr, 'omnifunc', 'v:lua.vim.lsp.omnifunc')
|
||||
end
|
||||
require('lspconfig').gopls.setup({
|
||||
on_attach = on_attach
|
||||
})
|
||||
```
|
||||
|
||||
### <a href="#neovim-links" id="neovim-links">Additional Links</a>
|
||||
|
||||
* [Neovim's official LSP documentation][nvim-docs].
|
||||
|
||||
[vim-go]: https://github.com/fatih/vim-go
|
||||
[LanguageClient-neovim]: https://github.com/autozimu/LanguageClient-neovim
|
||||
[ale]: https://github.com/w0rp/ale
|
||||
[ale-issue-2179]: https://github.com/w0rp/ale/issues/2179
|
||||
[prabirshrestha/vim-lsp]: https://github.com/prabirshrestha/vim-lsp/
|
||||
[natebosch/vim-lsc]: https://github.com/natebosch/vim-lsc/
|
||||
[natebosch/vim-lsc#180]: https://github.com/natebosch/vim-lsc/issues/180
|
||||
[coc.nvim]: https://github.com/neoclide/coc.nvim/
|
||||
[`govim`]: https://github.com/myitcv/govim
|
||||
[govim-install]: https://github.com/myitcv/govim/blob/master/README.md#govim---go-development-plugin-for-vim8
|
||||
[nvim-docs]: https://neovim.io/doc/user/lsp.html
|
||||
[nvim-install]: https://github.com/neovim/neovim/wiki/Installing-Neovim
|
||||
[nvim-lspconfig]: https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md#gopls
|
||||
[nvim-lspconfig-imports]: https://github.com/neovim/nvim-lspconfig/issues/115
|
||||
@@ -0,0 +1,139 @@
|
||||
# Setting up your workspace
|
||||
|
||||
In the language server protocol, a "workspace" consists of a folder along with
|
||||
per-folder configuration. Some LSP clients such as VS Code allow configuring
|
||||
workspaces explicitly, while others do so automatically by looking for special
|
||||
files defining a workspace root (such as a `.git` directory or `go.mod` file).
|
||||
|
||||
In order to function, gopls needs a defined scope in which language features
|
||||
like references, rename, and implementation should operate. Put differently,
|
||||
gopls needs to infer from the LSP workspace which `go build` invocations you
|
||||
would use to build your workspace, including the working directory,
|
||||
environment, and build flags.
|
||||
|
||||
In the past, it could be tricky to set up your workspace so that gopls would
|
||||
infer the correct build information. It required opening the correct directory
|
||||
or using a `go.work` file to tell gopls about the modules you're working on,
|
||||
and configuring the correct operating system and architecture in advance.
|
||||
When this didn't work as expected, gopls would often fail in mysterious
|
||||
ways--the dreaded "No packages found" error.
|
||||
|
||||
Starting with gopls v0.15.0, workspace configuration is much simpler, and gopls
|
||||
will typically work when you open a Go file anywhere in your workspace. If it
|
||||
isn't working for you, or if you want to better understand how gopls models
|
||||
your workspace, please read on.
|
||||
|
||||
## Workspace builds
|
||||
|
||||
Starting with gopls v0.15.0, gopls will guess the builds you are working on
|
||||
based on the set of open files. When you open a file in a workspace folder,
|
||||
gopls checks whether the file is contained in a module, `go.work` workspace, or
|
||||
GOPATH directory, and configures the build accordingly. Additionally, if you
|
||||
open a file that is constrained to a different operating system or
|
||||
architecture, for example opening `foo_windows.go` when working on Linux, gopls
|
||||
will create a scope with `GOOS` and `GOARCH` set to a value that matches the
|
||||
file.
|
||||
|
||||
For example, suppose we had a repository with three modules: `moda`, `modb`,
|
||||
and `modc`, and a `go.work` file using modules `moda` and `modb`. If we open
|
||||
the files `moda/a.go`, `modb/b.go`, `moda/a_windows.go`, and `modc/c.go`, gopls
|
||||
will automatically create three builds:
|
||||
|
||||

|
||||
|
||||
This allows gopls to _just work_ when you open a Go file, but it does come with
|
||||
several caveats:
|
||||
|
||||
- It causes gopls to do more work, since it is now tracking three builds
|
||||
instead of one. However, the recent
|
||||
[scalability redesign](https://go.dev/blog/gopls-scalability)
|
||||
allows much of this work to be avoided through efficient caching.
|
||||
- For operations invoked from a given file, such as "References"
|
||||
or "Implementations", gopls executes the operation in
|
||||
_the default build for that file_. For example, finding references to
|
||||
a symbol `S` from `foo_linux.go` will return references from the Linux build,
|
||||
and finding references to the same symbol `S` from `foo_windows.go` will
|
||||
return references from the Windows build. Gopls searches the default build
|
||||
for the file, but it doesn't search all the other possible builds (even
|
||||
though that would be nice) because it is liable to be too expensive.
|
||||
Issues [#65757](https://go.dev/issue/65757) and
|
||||
[#65755](https://go.dev/issue/65755) propose improvements to this behavior.
|
||||
- When selecting a `GOOS/GOARCH` combination to match a build-constrained file,
|
||||
gopls will choose the first matching combination from
|
||||
[this list](https://cs.opensource.google/go/x/tools/+/master:gopls/internal/cache/port.go;l=30;drc=f872b3d6f05822d290bc7bdd29db090fd9d89f5c).
|
||||
In some cases, that may be surprising.
|
||||
- When working in a `GOOS/GOARCH` constrained file that does not match your
|
||||
default toolchain, `CGO_ENABLED=0` is implicitly set, since a C toolchain for
|
||||
that target is unlikely to be available. This means that gopls will not
|
||||
work in files including `import "C"`. Issue
|
||||
[#65758](https://go.dev/issue/65758) may lead to improvements in this
|
||||
behavior.
|
||||
- Gopls is currently unable to guess build flags that include arbitrary
|
||||
user-defined build constraints, such as a file with the build directive
|
||||
`//go:build mytag`. Issue [#65089](https://go.dev/issue/65089) proposes
|
||||
a heuristic by which gopls could handle this automatically.
|
||||
|
||||
Please provide feedback on this behavior by upvoting or commenting the issues
|
||||
mentioned above, or opening a [new issue](https://go.dev/issue/new) for other
|
||||
improvements you'd like to see.
|
||||
|
||||
## When to use a `go.work` file for development
|
||||
|
||||
Starting with Go 1.18, the `go` command has built-in support for multi-module
|
||||
workspaces specified by [`go.work`](https://go.dev/ref/mod#workspaces) files.
|
||||
Gopls will recognize these files if they are present in your workspace.
|
||||
|
||||
Use a `go.work` file when:
|
||||
|
||||
- you want to work on multiple modules simultaneously in a single logical
|
||||
build, for example if you want changes to one module to be reflected in
|
||||
another.
|
||||
- you want to improve gopls' memory usage or performance by reducing the number
|
||||
of builds it must track.
|
||||
- you want gopls to know which modules you are working on in a multi-module
|
||||
workspace, without opening any files. For example, it may be convenient to use
|
||||
`workspace/symbol` queries before any files are open.
|
||||
- you are using gopls v0.14.2 or earlier, and want to work on multiple
|
||||
modules.
|
||||
|
||||
For example, suppose this repo is checked out into the `$WORK/tools` directory,
|
||||
and [`x/mod`](https://pkg.go.dev/golang.org/x/mod) is checked out into
|
||||
`$WORK/mod`, and you are working on a new `x/mod` API for editing `go.mod`
|
||||
files that you want to simultaneously integrate into gopls.
|
||||
|
||||
You can work on both `golang.org/x/tools/gopls` and `golang.org/x/mod`
|
||||
simultaneously by creating a `go.work` file:
|
||||
|
||||
```sh
|
||||
cd $WORK
|
||||
go work init
|
||||
go work use tools/gopls mod
|
||||
```
|
||||
|
||||
then opening the `$WORK` directory in your editor.
|
||||
|
||||
## When to manually configure `GOOS`, `GOARCH`, or `-tags`
|
||||
|
||||
As described in the first section, gopls v0.15.0 and later will try to
|
||||
configure a new build scope automatically when you open a file that doesn't
|
||||
match the system default operating system (`GOOS`) or architecture (`GOARCH`).
|
||||
|
||||
However, per the caveats listed in that section, this automatic behavior comes
|
||||
with limitations. Customize your gopls environment by setting `GOOS` or
|
||||
`GOARCH` in your
|
||||
[`"build.env"`](https://github.com/golang/tools/blob/master/gopls/doc/settings.md#env)
|
||||
or `-tags=...` in your"
|
||||
["build.buildFlags"](https://github.com/golang/tools/blob/master/gopls/doc/settings.md#buildflags)
|
||||
when:
|
||||
|
||||
- You want to modify the default build environment.
|
||||
- Gopls is not guessing the `GOOS/GOARCH` combination you want to use for
|
||||
cross platform development.
|
||||
- You need to work on a file that is constrained by a user-defined build tags,
|
||||
such as the build directive `//go:build mytag`.
|
||||
|
||||
## GOPATH mode
|
||||
|
||||
When opening a directory within a `GOPATH` directory, the workspace scope will
|
||||
be just that directory and all directories contained within it. Note that
|
||||
opening a large GOPATH directory can make gopls very slow to start.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
@@ -0,0 +1,27 @@
|
||||
module golang.org/x/tools/gopls
|
||||
|
||||
go 1.19 // => default GODEBUG has gotypesalias=0
|
||||
|
||||
require (
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/jba/templatecheck v0.7.0
|
||||
golang.org/x/mod v0.20.0
|
||||
golang.org/x/sync v0.8.0
|
||||
golang.org/x/telemetry v0.0.0-20240829154258-f29ab539cc98
|
||||
golang.org/x/text v0.16.0
|
||||
golang.org/x/tools v0.22.1-0.20240829175637-39126e24d653
|
||||
golang.org/x/vuln v1.0.4
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
honnef.co/go/tools v0.4.7
|
||||
mvdan.cc/gofumpt v0.6.0
|
||||
mvdan.cc/xurls/v2 v2.5.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.2.1 // indirect
|
||||
github.com/google/safehtml v0.1.0 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20221212164502-fae10dda9338 // indirect
|
||||
golang.org/x/sys v0.23.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
|
||||
)
|
||||
@@ -0,0 +1,41 @@
|
||||
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/safehtml v0.1.0 h1:EwLKo8qawTKfsi0orxcQAZzu07cICaBeFMegAU9eaT8=
|
||||
github.com/google/safehtml v0.1.0/go.mod h1:L4KWwDsUJdECRAEpZoBn3O64bQaywRscowZjJAzjHnU=
|
||||
github.com/jba/templatecheck v0.7.0 h1:wjTb/VhGgSFeim5zjWVePBdaMo28X74bGLSABZV+zIA=
|
||||
github.com/jba/templatecheck v0.7.0/go.mod h1:n1Etw+Rrw1mDDD8dDRsEKTwMZsJ98EkktgNJC6wLUGo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
golang.org/x/exp/typeparams v0.0.0-20221212164502-fae10dda9338 h1:2O2DON6y3XMJiQRAS1UWU+54aec2uopH3x7MAiqGW6Y=
|
||||
golang.org/x/exp/typeparams v0.0.0-20221212164502-fae10dda9338/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
|
||||
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/telemetry v0.0.0-20240829154258-f29ab539cc98 h1:Wm3cG5X6sZ0RSVRc/H1/sciC4AT6HAKgLCSH2lbpR/c=
|
||||
golang.org/x/telemetry v0.0.0-20240829154258-f29ab539cc98/go.mod h1:m7R/r+o5h7UvF2JD9n2iLSGY4v8v+zNSyTJ6xynLrqs=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.22.1-0.20240829175637-39126e24d653 h1:6bJEg2w2kUHWlfdJaESYsmNfI1LKAZQi6zCa7LUn7eI=
|
||||
golang.org/x/tools v0.22.1-0.20240829175637-39126e24d653/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
golang.org/x/vuln v1.0.4 h1:SP0mPeg2PmGCu03V+61EcQiOjmpri2XijexKdzv8Z1I=
|
||||
golang.org/x/vuln v1.0.4/go.mod h1:NbJdUQhX8jY++FtuhrXs2Eyx0yePo9pF7nPlIjo9aaQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.4.7 h1:9MDAWxMoSnB6QoSqiVr7P5mtkT9pOc1kSxchzPCnqJs=
|
||||
honnef.co/go/tools v0.4.7/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0=
|
||||
mvdan.cc/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo=
|
||||
mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA=
|
||||
mvdan.cc/xurls/v2 v2.5.0 h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh+8=
|
||||
mvdan.cc/xurls/v2 v2.5.0/go.mod h1:yQgaGQ1rFtJUzkmKiHYSSfuQxqfYmd//X6PxvholpeE=
|
||||
@@ -0,0 +1,16 @@
|
||||
# Copyright 2019 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.
|
||||
|
||||
# govim requires a more recent version of vim than is available in most
|
||||
# distros, so we build from their base image.
|
||||
FROM govim/govim:latest-vim
|
||||
ARG GOVIM_REF
|
||||
|
||||
ENV GOPROXY=https://proxy.golang.org GOPATH=/go VIM_FLAVOR=vim
|
||||
WORKDIR /src
|
||||
|
||||
# Clone govim. In order to use the go command for resolving latest, we download
|
||||
# a redundant copy of govim to the build cache using `go mod download`.
|
||||
RUN git clone https://github.com/govim/govim /src/govim && cd /src/govim && \
|
||||
git checkout $GOVIM_REF
|
||||
@@ -0,0 +1,47 @@
|
||||
# govim integration tests
|
||||
|
||||
Files in this directory configure Cloud Build to run [govim] integration tests
|
||||
against a gopls binary built from source.
|
||||
|
||||
## Running on GCP
|
||||
|
||||
To run these integration tests in Cloud Build, use the following steps. Here
|
||||
we assume that `$PROJECT_ID` is a valid GCP project and `$BUCKET` is a cloud
|
||||
storage bucket owned by that project.
|
||||
|
||||
- `cd` to the root directory of the tools project.
|
||||
- (at least once per GCP project) Build the test harness:
|
||||
```
|
||||
$ gcloud builds submit \
|
||||
--project="${PROJECT_ID}" \
|
||||
--config=gopls/integration/govim/cloudbuild.harness.yaml
|
||||
```
|
||||
- Run the integration tests:
|
||||
```
|
||||
$ gcloud builds submit \
|
||||
--project="${PROJECT_ID}" \
|
||||
--config=gopls/integration/govim/cloudbuild.yaml \
|
||||
--substitutions=_RESULT_BUCKET="${BUCKET}"
|
||||
```
|
||||
|
||||
## Fetching Artifacts
|
||||
|
||||
Assuming the artifacts bucket is world readable, you can fetch integration from
|
||||
GCS. They are located at:
|
||||
|
||||
- logs: `https://storage.googleapis.com/${BUCKET}/log-${EVALUATION_ID}.txt`
|
||||
- artifact tarball: `https://storage.googleapis.com/${BUCKET}/govim/${EVALUATION_ID}/artifacts.tar.gz`
|
||||
|
||||
The `artifacts.go` command can be used to fetch both artifacts using an
|
||||
evaluation id.
|
||||
|
||||
## Running locally
|
||||
|
||||
Run `gopls/integration/govim/run_local.sh`. This may take a while the first
|
||||
time it is run, as it will require building the test harness. This script
|
||||
accepts two flags to modify its behavior:
|
||||
|
||||
**--sudo**: run docker with `sudo`
|
||||
**--short**: run `go test -short`
|
||||
|
||||
[govim]: https://github.com/govim/govim
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright 2020 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 main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
var bucket = flag.String("bucket", "golang-gopls_integration_tests", "GCS bucket holding test artifacts.")
|
||||
|
||||
const usage = `
|
||||
artifacts [--bucket=<bucket ID>] <cloud build evaluation ID>
|
||||
|
||||
Fetch artifacts from an integration test run. Evaluation ID should be extracted
|
||||
from the cloud build notification.
|
||||
|
||||
In order for this to work, the GCS bucket that artifacts were written to must
|
||||
be publicly readable. By default, this fetches from the
|
||||
golang-gopls_integration_tests bucket.
|
||||
`
|
||||
|
||||
func main() {
|
||||
flag.Usage = func() {
|
||||
fmt.Fprint(flag.CommandLine.Output(), usage)
|
||||
}
|
||||
flag.Parse()
|
||||
if flag.NArg() != 1 {
|
||||
flag.Usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
evalID := flag.Arg(0)
|
||||
logURL := fmt.Sprintf("https://storage.googleapis.com/%s/log-%s.txt", *bucket, evalID)
|
||||
if err := download(logURL); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "downloading logs: %v", err)
|
||||
}
|
||||
tarURL := fmt.Sprintf("https://storage.googleapis.com/%s/govim/%s/artifacts.tar.gz", *bucket, evalID)
|
||||
if err := download(tarURL); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "downloading artifact tarball: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func download(artifactURL string) error {
|
||||
name := path.Base(artifactURL)
|
||||
resp, err := http.Get(artifactURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetching from GCS: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("got status code %d from GCS", resp.StatusCode)
|
||||
}
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading result: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(name, data, 0644); err != nil {
|
||||
return fmt.Errorf("writing artifact: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
# Copyright 2019 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.
|
||||
|
||||
# Build the govim test harness that will be used to run govim integration tests
|
||||
# for gopls. See README.md for instructions on how to use this.
|
||||
steps:
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
args: ['build',
|
||||
# To allow for breaking changes to this test harness, tag with a major
|
||||
# version number.
|
||||
'-t', 'gcr.io/$PROJECT_ID/govim-harness:latest',
|
||||
'-t', 'gcr.io/$PROJECT_ID/govim-harness:3',
|
||||
# It is assumed that this build is running from the root directory of the
|
||||
# tools repository.
|
||||
'-f', 'gopls/integration/govim/Dockerfile',
|
||||
# Use the integration test directory as build context: the test harness
|
||||
# doesn't actually require any local files.
|
||||
'gopls/integration/govim']
|
||||
images:
|
||||
- gcr.io/$PROJECT_ID/govim-harness
|
||||
@@ -0,0 +1,51 @@
|
||||
# Copyright 2019 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.
|
||||
|
||||
# Build gopls, and run the govim integration tests. See README.md for
|
||||
# instructions on how to use this.
|
||||
|
||||
substitutions:
|
||||
# This bucket must be owned by the GCP project executing the build. If
|
||||
# you are running this from your own project, override using --substitutions.
|
||||
_RESULT_BUCKET: 'golang-gopls_integration_tests'
|
||||
|
||||
steps:
|
||||
# Build gopls from source, to use with the govim integration tests.
|
||||
- name: 'golang:1.14'
|
||||
env: ['GOPROXY=https://proxy.golang.org']
|
||||
dir: 'gopls'
|
||||
args: ['go', 'build']
|
||||
|
||||
# Run the tests. Note that the script in this step does not return the exit
|
||||
# code from `go test`, but rather saves it for use in the final step after
|
||||
# uploading artifacts.
|
||||
- name: 'gcr.io/$PROJECT_ID/govim-harness:3'
|
||||
dir: '/src/govim'
|
||||
volumes:
|
||||
- name: artifacts
|
||||
path: /artifacts
|
||||
env:
|
||||
- GOVIM_TESTSCRIPT_WORKDIR_ROOT=/artifacts
|
||||
- VIM_FLAVOR=vim
|
||||
args: ['/workspace/gopls/integration/govim/run_tests_for_cloudbuild.sh']
|
||||
|
||||
# The govim tests produce a large number of artifacts; tarball/gzip to reduce
|
||||
# roundtrips and save space.
|
||||
- name: 'ubuntu'
|
||||
volumes:
|
||||
- name: artifacts
|
||||
path: /artifacts
|
||||
args: ['tar', '-czf', 'artifacts.tar.gz', '/artifacts']
|
||||
|
||||
# Upload artifacts to GCS.
|
||||
- name: 'gcr.io/cloud-builders/gsutil'
|
||||
args: ['cp', 'artifacts.tar.gz', 'gs://${_RESULT_BUCKET}/govim/${BUILD_ID}/artifacts.tar.gz']
|
||||
|
||||
# Exit with the actual exit code of the integration tests.
|
||||
- name: 'ubuntu'
|
||||
args: ['bash', 'govim_test_result.sh']
|
||||
|
||||
# Write build logs to the same bucket as artifacts, so they can be more easily
|
||||
# shared.
|
||||
logsBucket: 'gs://${_RESULT_BUCKET}'
|
||||
@@ -0,0 +1,96 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
# Copyright 2019 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.
|
||||
|
||||
# Run govim integration tests against a local gopls.
|
||||
|
||||
usage() {
|
||||
cat <<EOUSAGE
|
||||
Usage: $0 [--sudo] [--short] [--version (semver|latest)]
|
||||
|
||||
Args:
|
||||
--sudo run docker with sudo
|
||||
--short run `go test` with `-short`
|
||||
--version run on the specific tagged govim version (or latest) rather
|
||||
than the default branch
|
||||
|
||||
Run govim tests against HEAD using local docker.
|
||||
EOUSAGE
|
||||
}
|
||||
|
||||
SUDO_IF_NEEDED=
|
||||
TEST_SHORT=
|
||||
DOCKERFILE=gopls/integration/govim/Dockerfile
|
||||
GOVIM_REF=main
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
"-h" | "--help" | "help")
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
"--sudo")
|
||||
SUDO_IF_NEEDED="sudo "
|
||||
shift
|
||||
;;
|
||||
"--short")
|
||||
TEST_SHORT="-short"
|
||||
shift
|
||||
;;
|
||||
"--version")
|
||||
if [[ -z "$2" ]]; then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
GOVIM_REF=$2
|
||||
if [[ "${GOVIM_REF}" == "latest" ]]; then
|
||||
TMPGOPATH=$(mktemp -d)
|
||||
trap "GOPATH=${TMPGOPATH} go clean -modcache && rm -r ${TMPGOPATH}" EXIT
|
||||
GOVIM_REF=$(GOPATH=${TMPGOPATH} go mod download -json \
|
||||
github.com/govim/govim@latest | jq -r .Version)
|
||||
fi
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
exit 1
|
||||
esac
|
||||
done
|
||||
|
||||
# Find the tools root, so that this script can be run from any directory.
|
||||
script_dir=$(dirname "$(readlink -f "$0")")
|
||||
tools_dir=$(readlink -f "${script_dir}/../../..")
|
||||
|
||||
# Build gopls.
|
||||
cd "${tools_dir}/gopls"
|
||||
temp_gopls=$(mktemp -p "$PWD")
|
||||
trap "rm -f \"${temp_gopls}\"" EXIT
|
||||
# For consistency across environments, use golang docker to build rather than
|
||||
# the local go command.
|
||||
${SUDO_IF_NEEDED}docker run --rm -t \
|
||||
-v "${tools_dir}:/src/tools" \
|
||||
-w "/src/tools/gopls" \
|
||||
golang:rc \
|
||||
go build -o $(basename ${temp_gopls})
|
||||
|
||||
# Build the test harness. Here we are careful to pass in a very limited build
|
||||
# context so as to optimize caching.
|
||||
echo "Checking out govim@${GOVIM_REF}"
|
||||
cd "${tools_dir}"
|
||||
${SUDO_IF_NEEDED}docker build \
|
||||
--build-arg GOVIM_REF="${GOVIM_REF}" \
|
||||
-t gopls-govim-harness:${GOVIM_REF} \
|
||||
-f gopls/integration/govim/Dockerfile \
|
||||
gopls/integration/govim
|
||||
|
||||
# Run govim integration tests.
|
||||
echo "running govim integration tests using ${temp_gopls}"
|
||||
temp_gopls_name=$(basename "${temp_gopls}")
|
||||
${SUDO_IF_NEEDED}docker run --rm -t \
|
||||
-v "${tools_dir}:/src/tools" \
|
||||
-w "/src/govim" \
|
||||
--ulimit memlock=-1:-1 \
|
||||
gopls-govim-harness:${GOVIM_REF} \
|
||||
go test ${TEST_SHORT} ./cmd/govim \
|
||||
-gopls "/src/tools/gopls/${temp_gopls_name}"
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright 2020 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.
|
||||
|
||||
# This script runs govim integration tests but always succeeds, instead writing
|
||||
# their result to a file so that any test failure can be deferred to a later
|
||||
# build step. We do this so that we can capture govim test artifacts regardless
|
||||
# of the test results.
|
||||
|
||||
# Substitute the locally built gopls binary for use in govim integration tests.
|
||||
go test -short ./cmd/govim -gopls /workspace/gopls/gopls
|
||||
|
||||
# Stash the error, for use in a later build step.
|
||||
echo "exit $?" > /workspace/govim_test_result.sh
|
||||
|
||||
# Clean up unnecessary artifacts. This is based on govim/_scripts/tidyUp.bash.
|
||||
# Since we're fetching govim using the go command, we won't have this non-go
|
||||
# source directory available to us.
|
||||
if [[ -n "$GOVIM_TESTSCRIPT_WORKDIR_ROOT" ]]; then
|
||||
echo "Cleaning up build artifacts..."
|
||||
# Make artifacts writable so that rm -rf doesn't complain.
|
||||
chmod -R u+w "$GOVIM_TESTSCRIPT_WORKDIR_ROOT"
|
||||
|
||||
# Remove directories we don't care about.
|
||||
find "$GOVIM_TESTSCRIPT_WORKDIR_ROOT" -type d \( -name .vim -o -name gopath \) -prune -exec rm -rf '{}' \;
|
||||
fi
|
||||
+267
@@ -0,0 +1,267 @@
|
||||
// Copyright 2023 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 deprecated
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
_ "embed"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
"golang.org/x/tools/go/ast/inspector"
|
||||
"golang.org/x/tools/internal/analysisinternal"
|
||||
)
|
||||
|
||||
//go:embed doc.go
|
||||
var doc string
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "deprecated",
|
||||
Doc: analysisinternal.MustExtractDoc(doc, "deprecated"),
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
Run: checkDeprecated,
|
||||
FactTypes: []analysis.Fact{(*deprecationFact)(nil)},
|
||||
RunDespiteErrors: true,
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/deprecated",
|
||||
}
|
||||
|
||||
// checkDeprecated is a simplified copy of staticcheck.CheckDeprecated.
|
||||
func checkDeprecated(pass *analysis.Pass) (interface{}, error) {
|
||||
inspector := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
||||
|
||||
deprs, err := collectDeprecatedNames(pass, inspector)
|
||||
if err != nil || (len(deprs.packages) == 0 && len(deprs.objects) == 0) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reportDeprecation := func(depr *deprecationFact, node ast.Node) {
|
||||
// TODO(hyangah): staticcheck.CheckDeprecated has more complex logic. Do we need it here?
|
||||
// TODO(hyangah): Scrub depr.Msg. depr.Msg may contain Go comments
|
||||
// markdown syntaxes but LSP diagnostics do not support markdown syntax.
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
if err := format.Node(buf, pass.Fset, node); err != nil {
|
||||
// This shouldn't happen but let's be conservative.
|
||||
buf.Reset()
|
||||
buf.WriteString("declaration")
|
||||
}
|
||||
pass.ReportRangef(node, "%s is deprecated: %s", buf, depr.Msg)
|
||||
}
|
||||
|
||||
nodeFilter := []ast.Node{(*ast.SelectorExpr)(nil)}
|
||||
inspector.Preorder(nodeFilter, func(node ast.Node) {
|
||||
// Caveat: this misses dot-imported objects
|
||||
sel, ok := node.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
obj := pass.TypesInfo.ObjectOf(sel.Sel)
|
||||
if fn, ok := obj.(*types.Func); ok {
|
||||
obj = fn.Origin()
|
||||
}
|
||||
if obj == nil || obj.Pkg() == nil {
|
||||
// skip invalid sel.Sel.
|
||||
return
|
||||
}
|
||||
|
||||
if obj.Pkg() == pass.Pkg {
|
||||
// A package is allowed to use its own deprecated objects
|
||||
return
|
||||
}
|
||||
|
||||
// A package "foo" has two related packages "foo_test" and "foo.test", for external tests and the package main
|
||||
// generated by 'go test' respectively. "foo_test" can import and use "foo", "foo.test" imports and uses "foo"
|
||||
// and "foo_test".
|
||||
|
||||
if strings.TrimSuffix(pass.Pkg.Path(), "_test") == obj.Pkg().Path() {
|
||||
// foo_test (the external tests of foo) can use objects from foo.
|
||||
return
|
||||
}
|
||||
if strings.TrimSuffix(pass.Pkg.Path(), ".test") == obj.Pkg().Path() {
|
||||
// foo.test (the main package of foo's tests) can use objects from foo.
|
||||
return
|
||||
}
|
||||
if strings.TrimSuffix(pass.Pkg.Path(), ".test") == strings.TrimSuffix(obj.Pkg().Path(), "_test") {
|
||||
// foo.test (the main package of foo's tests) can use objects from foo's external tests.
|
||||
return
|
||||
}
|
||||
|
||||
if depr, ok := deprs.objects[obj]; ok {
|
||||
reportDeprecation(depr, sel)
|
||||
}
|
||||
})
|
||||
|
||||
for _, f := range pass.Files {
|
||||
for _, spec := range f.Imports {
|
||||
var imp *types.Package
|
||||
var obj types.Object
|
||||
if spec.Name != nil {
|
||||
obj = pass.TypesInfo.ObjectOf(spec.Name)
|
||||
} else {
|
||||
obj = pass.TypesInfo.Implicits[spec]
|
||||
}
|
||||
pkgName, ok := obj.(*types.PkgName)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
imp = pkgName.Imported()
|
||||
|
||||
path, err := strconv.Unquote(spec.Path.Value)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
pkgPath := pass.Pkg.Path()
|
||||
if strings.TrimSuffix(pkgPath, "_test") == path {
|
||||
// foo_test can import foo
|
||||
continue
|
||||
}
|
||||
if strings.TrimSuffix(pkgPath, ".test") == path {
|
||||
// foo.test can import foo
|
||||
continue
|
||||
}
|
||||
if strings.TrimSuffix(pkgPath, ".test") == strings.TrimSuffix(path, "_test") {
|
||||
// foo.test can import foo_test
|
||||
continue
|
||||
}
|
||||
if depr, ok := deprs.packages[imp]; ok {
|
||||
reportDeprecation(depr, spec.Path)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type deprecationFact struct{ Msg string }
|
||||
|
||||
func (*deprecationFact) AFact() {}
|
||||
func (d *deprecationFact) String() string { return "Deprecated: " + d.Msg }
|
||||
|
||||
type deprecatedNames struct {
|
||||
objects map[types.Object]*deprecationFact
|
||||
packages map[*types.Package]*deprecationFact
|
||||
}
|
||||
|
||||
// collectDeprecatedNames collects deprecated identifiers and publishes
|
||||
// them both as Facts and the return value. This is a simplified copy
|
||||
// of staticcheck's fact_deprecated analyzer.
|
||||
func collectDeprecatedNames(pass *analysis.Pass, ins *inspector.Inspector) (deprecatedNames, error) {
|
||||
extractDeprecatedMessage := func(docs []*ast.CommentGroup) string {
|
||||
for _, doc := range docs {
|
||||
if doc == nil {
|
||||
continue
|
||||
}
|
||||
parts := strings.Split(doc.Text(), "\n\n")
|
||||
for _, part := range parts {
|
||||
if !strings.HasPrefix(part, "Deprecated: ") {
|
||||
continue
|
||||
}
|
||||
alt := part[len("Deprecated: "):]
|
||||
alt = strings.Replace(alt, "\n", " ", -1)
|
||||
return strings.TrimSpace(alt)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
doDocs := func(names []*ast.Ident, docs *ast.CommentGroup) {
|
||||
alt := extractDeprecatedMessage([]*ast.CommentGroup{docs})
|
||||
if alt == "" {
|
||||
return
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
obj := pass.TypesInfo.ObjectOf(name)
|
||||
pass.ExportObjectFact(obj, &deprecationFact{alt})
|
||||
}
|
||||
}
|
||||
|
||||
var docs []*ast.CommentGroup
|
||||
for _, f := range pass.Files {
|
||||
docs = append(docs, f.Doc)
|
||||
}
|
||||
if alt := extractDeprecatedMessage(docs); alt != "" {
|
||||
// Don't mark package syscall as deprecated, even though
|
||||
// it is. A lot of people still use it for simple
|
||||
// constants like SIGKILL, and I am not comfortable
|
||||
// telling them to use x/sys for that.
|
||||
if pass.Pkg.Path() != "syscall" {
|
||||
pass.ExportPackageFact(&deprecationFact{alt})
|
||||
}
|
||||
}
|
||||
nodeFilter := []ast.Node{
|
||||
(*ast.GenDecl)(nil),
|
||||
(*ast.FuncDecl)(nil),
|
||||
(*ast.TypeSpec)(nil),
|
||||
(*ast.ValueSpec)(nil),
|
||||
(*ast.File)(nil),
|
||||
(*ast.StructType)(nil),
|
||||
(*ast.InterfaceType)(nil),
|
||||
}
|
||||
ins.Preorder(nodeFilter, func(node ast.Node) {
|
||||
var names []*ast.Ident
|
||||
var docs *ast.CommentGroup
|
||||
switch node := node.(type) {
|
||||
case *ast.GenDecl:
|
||||
switch node.Tok {
|
||||
case token.TYPE, token.CONST, token.VAR:
|
||||
docs = node.Doc
|
||||
for i := range node.Specs {
|
||||
switch n := node.Specs[i].(type) {
|
||||
case *ast.ValueSpec:
|
||||
names = append(names, n.Names...)
|
||||
case *ast.TypeSpec:
|
||||
names = append(names, n.Name)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
case *ast.FuncDecl:
|
||||
docs = node.Doc
|
||||
names = []*ast.Ident{node.Name}
|
||||
case *ast.TypeSpec:
|
||||
docs = node.Doc
|
||||
names = []*ast.Ident{node.Name}
|
||||
case *ast.ValueSpec:
|
||||
docs = node.Doc
|
||||
names = node.Names
|
||||
case *ast.StructType:
|
||||
for _, field := range node.Fields.List {
|
||||
doDocs(field.Names, field.Doc)
|
||||
}
|
||||
case *ast.InterfaceType:
|
||||
for _, field := range node.Methods.List {
|
||||
doDocs(field.Names, field.Doc)
|
||||
}
|
||||
}
|
||||
if docs != nil && len(names) > 0 {
|
||||
doDocs(names, docs)
|
||||
}
|
||||
})
|
||||
|
||||
// Every identifier is potentially deprecated, so we will need
|
||||
// to look up facts a lot. Construct maps of all facts propagated
|
||||
// to this pass for fast lookup.
|
||||
out := deprecatedNames{
|
||||
objects: map[types.Object]*deprecationFact{},
|
||||
packages: map[*types.Package]*deprecationFact{},
|
||||
}
|
||||
for _, fact := range pass.AllObjectFacts() {
|
||||
out.objects[fact.Object] = fact.Fact.(*deprecationFact)
|
||||
}
|
||||
for _, fact := range pass.AllPackageFacts() {
|
||||
out.packages[fact.Package] = fact.Fact.(*deprecationFact)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// Copyright 2023 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 deprecated
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
testdata := analysistest.TestData()
|
||||
analysistest.Run(t, testdata, Analyzer, "a")
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2023 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 deprecated defines an Analyzer that marks deprecated symbols and package imports.
|
||||
//
|
||||
// # Analyzer deprecated
|
||||
//
|
||||
// deprecated: check for use of deprecated identifiers
|
||||
//
|
||||
// The deprecated analyzer looks for deprecated symbols and package
|
||||
// imports.
|
||||
//
|
||||
// See https://go.dev/wiki/Deprecated to learn about Go's convention
|
||||
// for documenting and signaling deprecated identifiers.
|
||||
package deprecated
|
||||
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
// Copyright 2023 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 usedeprecated
|
||||
|
||||
import "io/ioutil" // want "\"io/ioutil\" is deprecated: .*"
|
||||
|
||||
func x() {
|
||||
_, _ = ioutil.ReadFile("") // want "ioutil.ReadFile is deprecated: As of Go 1.16, .*"
|
||||
Legacy() // expect no deprecation notice.
|
||||
}
|
||||
|
||||
// Legacy is deprecated.
|
||||
//
|
||||
// Deprecated: use X instead.
|
||||
func Legacy() {} // want Legacy:"Deprecated: use X instead."
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
// Copyright 2023 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 usedeprecated
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestF(t *testing.T) {
|
||||
Legacy() // expect no deprecation notice.
|
||||
x()
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright 2023 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 embeddirective defines an Analyzer that validates //go:embed directives.
|
||||
// The analyzer defers fixes to its parent golang.Analyzer.
|
||||
//
|
||||
// # Analyzer embed
|
||||
//
|
||||
// embed: check //go:embed directive usage
|
||||
//
|
||||
// This analyzer checks that the embed package is imported if //go:embed
|
||||
// directives are present, providing a suggested fix to add the import if
|
||||
// it is missing.
|
||||
//
|
||||
// This analyzer also checks that //go:embed directives precede the
|
||||
// declaration of a single variable.
|
||||
package embeddirective
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
// Copyright 2022 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 embeddirective
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/internal/aliases"
|
||||
"golang.org/x/tools/internal/analysisinternal"
|
||||
)
|
||||
|
||||
//go:embed doc.go
|
||||
var doc string
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "embed",
|
||||
Doc: analysisinternal.MustExtractDoc(doc, "embed"),
|
||||
Run: run,
|
||||
RunDespiteErrors: true,
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/embeddirective",
|
||||
}
|
||||
|
||||
const FixCategory = "addembedimport" // recognized by gopls ApplyFix
|
||||
|
||||
func run(pass *analysis.Pass) (interface{}, error) {
|
||||
for _, f := range pass.Files {
|
||||
comments := embedDirectiveComments(f)
|
||||
if len(comments) == 0 {
|
||||
continue // nothing to check
|
||||
}
|
||||
|
||||
hasEmbedImport := false
|
||||
for _, imp := range f.Imports {
|
||||
if imp.Path.Value == `"embed"` {
|
||||
hasEmbedImport = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range comments {
|
||||
pos, end := c.Pos(), c.Pos()+token.Pos(len("//go:embed"))
|
||||
|
||||
if !hasEmbedImport {
|
||||
pass.Report(analysis.Diagnostic{
|
||||
Pos: pos,
|
||||
End: end,
|
||||
Message: `must import "embed" when using go:embed directives`,
|
||||
Category: FixCategory,
|
||||
SuggestedFixes: []analysis.SuggestedFix{{
|
||||
Message: `Add missing "embed" import`,
|
||||
// No TextEdits => computed by a gopls command.
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
var msg string
|
||||
spec := nextVarSpec(c, f)
|
||||
switch {
|
||||
case spec == nil:
|
||||
msg = `go:embed directives must precede a "var" declaration`
|
||||
case len(spec.Names) != 1:
|
||||
msg = "declarations following go:embed directives must define a single variable"
|
||||
case len(spec.Values) > 0:
|
||||
msg = "declarations following go:embed directives must not specify a value"
|
||||
case !embeddableType(pass.TypesInfo.Defs[spec.Names[0]]):
|
||||
msg = "declarations following go:embed directives must be of type string, []byte or embed.FS"
|
||||
}
|
||||
if msg != "" {
|
||||
pass.Report(analysis.Diagnostic{
|
||||
Pos: pos,
|
||||
End: end,
|
||||
Message: msg,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// embedDirectiveComments returns all comments in f that contains a //go:embed directive.
|
||||
func embedDirectiveComments(f *ast.File) []*ast.Comment {
|
||||
comments := []*ast.Comment{}
|
||||
for _, cg := range f.Comments {
|
||||
for _, c := range cg.List {
|
||||
if strings.HasPrefix(c.Text, "//go:embed ") {
|
||||
comments = append(comments, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
return comments
|
||||
}
|
||||
|
||||
// nextVarSpec returns the ValueSpec for the variable declaration immediately following
|
||||
// the go:embed comment, or nil if the next declaration is not a variable declaration.
|
||||
func nextVarSpec(com *ast.Comment, f *ast.File) *ast.ValueSpec {
|
||||
// Embed directives must be followed by a declaration of one variable with no value.
|
||||
// There may be comments and empty lines between the directive and the declaration.
|
||||
var nextDecl ast.Decl
|
||||
for _, d := range f.Decls {
|
||||
if com.End() < d.End() {
|
||||
nextDecl = d
|
||||
break
|
||||
}
|
||||
}
|
||||
if nextDecl == nil || nextDecl.Pos() == token.NoPos {
|
||||
return nil
|
||||
}
|
||||
decl, ok := nextDecl.(*ast.GenDecl)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if decl.Tok != token.VAR {
|
||||
return nil
|
||||
}
|
||||
|
||||
// var declarations can be both freestanding and blocks (with parenthesis).
|
||||
// Only the first variable spec following the directive is interesting.
|
||||
var nextSpec ast.Spec
|
||||
for _, s := range decl.Specs {
|
||||
if com.End() < s.End() {
|
||||
nextSpec = s
|
||||
break
|
||||
}
|
||||
}
|
||||
if nextSpec == nil {
|
||||
return nil
|
||||
}
|
||||
spec, ok := nextSpec.(*ast.ValueSpec)
|
||||
if !ok {
|
||||
// Invalid AST, but keep going.
|
||||
return nil
|
||||
}
|
||||
return spec
|
||||
}
|
||||
|
||||
// embeddableType in go:embed directives are string, []byte or embed.FS.
|
||||
func embeddableType(o types.Object) bool {
|
||||
if o == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// For embed.FS the underlying type is an implementation detail.
|
||||
// As long as the named type resolves to embed.FS, it is OK.
|
||||
if named, ok := aliases.Unalias(o.Type()).(*types.Named); ok {
|
||||
obj := named.Obj()
|
||||
if obj.Pkg() != nil && obj.Pkg().Path() == "embed" && obj.Name() == "FS" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
switch v := o.Type().Underlying().(type) {
|
||||
case *types.Basic:
|
||||
return types.Identical(v, types.Typ[types.String])
|
||||
case *types.Slice:
|
||||
return types.Identical(v.Elem(), types.Typ[types.Byte])
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// Copyright 2022 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 embeddirective
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
testdata := analysistest.TestData()
|
||||
analysistest.RunWithSuggestedFixes(t, testdata, Analyzer, "a")
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
Hello World
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// Copyright 2022 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 a
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
//go:embed embedtext // want "must import \"embed\" when using go:embed directives"
|
||||
var s string
|
||||
|
||||
// This is main function
|
||||
func main() {
|
||||
fmt.Println(s)
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
// Copyright 2022 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 a
|
||||
|
||||
// Misplaced, above imports.
|
||||
//go:embed embedText // want "go:embed directives must precede a \"var\" declaration"
|
||||
|
||||
import (
|
||||
"embed"
|
||||
embedPkg "embed"
|
||||
"fmt"
|
||||
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed embedText // ok
|
||||
var e1 string
|
||||
|
||||
// The analyzer does not check for many directives using the same var.
|
||||
//
|
||||
//go:embed embedText // ok
|
||||
//go:embed embedText // ok
|
||||
var e2 string
|
||||
|
||||
// Comments and blank lines between are OK. All types OK.
|
||||
//
|
||||
//go:embed embedText // ok
|
||||
//
|
||||
// foo
|
||||
|
||||
var e3 string
|
||||
|
||||
//go:embed embedText //ok
|
||||
var e4 []byte
|
||||
|
||||
//go:embed embedText //ok
|
||||
var e5 embed.FS
|
||||
|
||||
// Followed by wrong kind of decl.
|
||||
//
|
||||
//go:embed embedText // want "go:embed directives must precede a \"var\" declaration"
|
||||
func fooFunc() {}
|
||||
|
||||
// Multiple variable specs.
|
||||
//
|
||||
//go:embed embedText // want "declarations following go:embed directives must define a single variable"
|
||||
var e6, e7 []byte
|
||||
|
||||
// Specifying a value is not allowed.
|
||||
//
|
||||
//go:embed embedText // want "declarations following go:embed directives must not specify a value"
|
||||
var e8 string = "foo"
|
||||
|
||||
// TODO: This should not be OK, misplaced according to compiler.
|
||||
//
|
||||
//go:embed embedText // ok
|
||||
var (
|
||||
e9 string
|
||||
e10 string
|
||||
)
|
||||
|
||||
// Type definition.
|
||||
type fooType []byte
|
||||
|
||||
//go:embed embedText //ok
|
||||
var e11 fooType
|
||||
|
||||
// Type alias.
|
||||
type barType = string
|
||||
|
||||
//go:embed embedText //ok
|
||||
var e12 barType
|
||||
|
||||
// Renamed embed package.
|
||||
|
||||
//go:embed embedText //ok
|
||||
var e13 embedPkg.FS
|
||||
|
||||
// Renamed embed package alias.
|
||||
type embedAlias = embedPkg.FS
|
||||
|
||||
//go:embed embedText //ok
|
||||
var e14 embedAlias
|
||||
|
||||
// var blocks are OK as long as the variable following the directive is OK.
|
||||
var (
|
||||
x, y, z string
|
||||
//go:embed embedText // ok
|
||||
e20 string
|
||||
q, r, t string
|
||||
)
|
||||
|
||||
//go:embed embedText // want "go:embed directives must precede a \"var\" declaration"
|
||||
var ()
|
||||
|
||||
// Incorrect types.
|
||||
|
||||
//go:embed embedText // want `declarations following go:embed directives must be of type string, \[\]byte or embed.FS`
|
||||
var e16 byte
|
||||
|
||||
//go:embed embedText // want `declarations following go:embed directives must be of type string, \[\]byte or embed.FS`
|
||||
var e17 []string
|
||||
|
||||
//go:embed embedText // want `declarations following go:embed directives must be of type string, \[\]byte or embed.FS`
|
||||
var e18 embed.Foo
|
||||
|
||||
//go:embed embedText // want `declarations following go:embed directives must be of type string, \[\]byte or embed.FS`
|
||||
var e19 foo.FS
|
||||
|
||||
type byteAlias byte
|
||||
|
||||
//go:embed embedText // want `declarations following go:embed directives must be of type string, \[\]byte or embed.FS`
|
||||
var e15 byteAlias
|
||||
|
||||
// A type declaration of embed.FS is not accepted by the compiler, in contrast to an alias.
|
||||
type embedDecl embed.FS
|
||||
|
||||
//go:embed embedText // want `declarations following go:embed directives must be of type string, \[\]byte or embed.FS`
|
||||
var e16 embedDecl
|
||||
|
||||
// This is main function
|
||||
func main() {
|
||||
fmt.Println(s)
|
||||
}
|
||||
|
||||
// No declaration following.
|
||||
//go:embed embedText // want "go:embed directives must precede a \"var\" declaration"
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
//go:build go1.20
|
||||
// +build go1.20
|
||||
|
||||
package a
|
||||
|
||||
var (
|
||||
// Okay directive wise but the compiler will complain that
|
||||
// imports must appear before other declarations.
|
||||
//go:embed embedText // ok
|
||||
foo string
|
||||
)
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
// This is main function
|
||||
func main() {
|
||||
fmt.Println(s)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2023 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 fillreturns defines an Analyzer that will attempt to
|
||||
// automatically fill in a return statement that has missing
|
||||
// values with zero value elements.
|
||||
//
|
||||
// # Analyzer fillreturns
|
||||
//
|
||||
// fillreturns: suggest fixes for errors due to an incorrect number of return values
|
||||
//
|
||||
// This checker provides suggested fixes for type errors of the
|
||||
// type "wrong number of return values (want %d, got %d)". For example:
|
||||
//
|
||||
// func m() (int, string, *bool, error) {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// will turn into
|
||||
//
|
||||
// func m() (int, string, *bool, error) {
|
||||
// return 0, "", nil, nil
|
||||
// }
|
||||
//
|
||||
// This functionality is similar to https://github.com/sqs/goreturns.
|
||||
package fillreturns
|
||||
+264
@@ -0,0 +1,264 @@
|
||||
// Copyright 2020 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 fillreturns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/types"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/internal/analysisinternal"
|
||||
"golang.org/x/tools/internal/fuzzy"
|
||||
)
|
||||
|
||||
//go:embed doc.go
|
||||
var doc string
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "fillreturns",
|
||||
Doc: analysisinternal.MustExtractDoc(doc, "fillreturns"),
|
||||
Run: run,
|
||||
RunDespiteErrors: true,
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/fillreturns",
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (interface{}, error) {
|
||||
info := pass.TypesInfo
|
||||
if info == nil {
|
||||
return nil, fmt.Errorf("nil TypeInfo")
|
||||
}
|
||||
|
||||
outer:
|
||||
for _, typeErr := range pass.TypeErrors {
|
||||
// Filter out the errors that are not relevant to this analyzer.
|
||||
if !FixesError(typeErr) {
|
||||
continue
|
||||
}
|
||||
var file *ast.File
|
||||
for _, f := range pass.Files {
|
||||
if f.Pos() <= typeErr.Pos && typeErr.Pos <= f.End() {
|
||||
file = f
|
||||
break
|
||||
}
|
||||
}
|
||||
if file == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the end position of the error.
|
||||
// (This heuristic assumes that the buffer is formatted,
|
||||
// at least up to the end position of the error.)
|
||||
var buf bytes.Buffer
|
||||
if err := format.Node(&buf, pass.Fset, file); err != nil {
|
||||
continue
|
||||
}
|
||||
typeErrEndPos := analysisinternal.TypeErrorEndPos(pass.Fset, buf.Bytes(), typeErr.Pos)
|
||||
|
||||
// TODO(rfindley): much of the error handling code below returns, when it
|
||||
// should probably continue.
|
||||
|
||||
// Get the path for the relevant range.
|
||||
path, _ := astutil.PathEnclosingInterval(file, typeErr.Pos, typeErrEndPos)
|
||||
if len(path) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Find the enclosing return statement.
|
||||
var ret *ast.ReturnStmt
|
||||
var retIdx int
|
||||
for i, n := range path {
|
||||
if r, ok := n.(*ast.ReturnStmt); ok {
|
||||
ret = r
|
||||
retIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if ret == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Get the function type that encloses the ReturnStmt.
|
||||
var enclosingFunc *ast.FuncType
|
||||
for _, n := range path[retIdx+1:] {
|
||||
switch node := n.(type) {
|
||||
case *ast.FuncLit:
|
||||
enclosingFunc = node.Type
|
||||
case *ast.FuncDecl:
|
||||
enclosingFunc = node.Type
|
||||
}
|
||||
if enclosingFunc != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if enclosingFunc == nil || enclosingFunc.Results == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip any generic enclosing functions, since type parameters don't
|
||||
// have 0 values.
|
||||
// TODO(rfindley): We should be able to handle this if the return
|
||||
// values are all concrete types.
|
||||
if tparams := enclosingFunc.TypeParams; tparams != nil && tparams.NumFields() > 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Find the function declaration that encloses the ReturnStmt.
|
||||
var outer *ast.FuncDecl
|
||||
for _, p := range path {
|
||||
if p, ok := p.(*ast.FuncDecl); ok {
|
||||
outer = p
|
||||
break
|
||||
}
|
||||
}
|
||||
if outer == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Skip any return statements that contain function calls with multiple
|
||||
// return values.
|
||||
for _, expr := range ret.Results {
|
||||
e, ok := expr.(*ast.CallExpr)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if tup, ok := info.TypeOf(e).(*types.Tuple); ok && tup.Len() > 1 {
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
|
||||
// Duplicate the return values to track which values have been matched.
|
||||
remaining := make([]ast.Expr, len(ret.Results))
|
||||
copy(remaining, ret.Results)
|
||||
|
||||
fixed := make([]ast.Expr, len(enclosingFunc.Results.List))
|
||||
|
||||
// For each value in the return function declaration, find the leftmost element
|
||||
// in the return statement that has the desired type. If no such element exists,
|
||||
// fill in the missing value with the appropriate "zero" value.
|
||||
// Beware that type information may be incomplete.
|
||||
var retTyps []types.Type
|
||||
for _, ret := range enclosingFunc.Results.List {
|
||||
retTyp := info.TypeOf(ret.Type)
|
||||
if retTyp == nil {
|
||||
return nil, nil
|
||||
}
|
||||
retTyps = append(retTyps, retTyp)
|
||||
}
|
||||
matches := analysisinternal.MatchingIdents(retTyps, file, ret.Pos(), info, pass.Pkg)
|
||||
for i, retTyp := range retTyps {
|
||||
var match ast.Expr
|
||||
var idx int
|
||||
for j, val := range remaining {
|
||||
if t := info.TypeOf(val); t == nil || !matchingTypes(t, retTyp) {
|
||||
continue
|
||||
}
|
||||
if !analysisinternal.IsZeroValue(val) {
|
||||
match, idx = val, j
|
||||
break
|
||||
}
|
||||
// If the current match is a "zero" value, we keep searching in
|
||||
// case we find a non-"zero" value match. If we do not find a
|
||||
// non-"zero" value, we will use the "zero" value.
|
||||
match, idx = val, j
|
||||
}
|
||||
|
||||
if match != nil {
|
||||
fixed[i] = match
|
||||
remaining = append(remaining[:idx], remaining[idx+1:]...)
|
||||
} else {
|
||||
names, ok := matches[retTyp]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid return type: %v", retTyp)
|
||||
}
|
||||
// Find the identifier most similar to the return type.
|
||||
// If no identifier matches the pattern, generate a zero value.
|
||||
if best := fuzzy.BestMatch(retTyp.String(), names); best != "" {
|
||||
fixed[i] = ast.NewIdent(best)
|
||||
} else if zero := analysisinternal.ZeroValue(file, pass.Pkg, retTyp); zero != nil {
|
||||
fixed[i] = zero
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any non-matching "zero values" from the leftover values.
|
||||
var nonZeroRemaining []ast.Expr
|
||||
for _, expr := range remaining {
|
||||
if !analysisinternal.IsZeroValue(expr) {
|
||||
nonZeroRemaining = append(nonZeroRemaining, expr)
|
||||
}
|
||||
}
|
||||
// Append leftover return values to end of new return statement.
|
||||
fixed = append(fixed, nonZeroRemaining...)
|
||||
|
||||
newRet := &ast.ReturnStmt{
|
||||
Return: ret.Pos(),
|
||||
Results: fixed,
|
||||
}
|
||||
|
||||
// Convert the new return statement AST to text.
|
||||
var newBuf bytes.Buffer
|
||||
if err := format.Node(&newBuf, pass.Fset, newRet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pass.Report(analysis.Diagnostic{
|
||||
Pos: typeErr.Pos,
|
||||
End: typeErrEndPos,
|
||||
Message: typeErr.Msg,
|
||||
SuggestedFixes: []analysis.SuggestedFix{{
|
||||
Message: "Fill in return values",
|
||||
TextEdits: []analysis.TextEdit{{
|
||||
Pos: ret.Pos(),
|
||||
End: ret.End(),
|
||||
NewText: newBuf.Bytes(),
|
||||
}},
|
||||
}},
|
||||
})
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func matchingTypes(want, got types.Type) bool {
|
||||
if want == got || types.Identical(want, got) {
|
||||
return true
|
||||
}
|
||||
// Code segment to help check for untyped equality from (golang/go#32146).
|
||||
if rhs, ok := want.(*types.Basic); ok && rhs.Info()&types.IsUntyped > 0 {
|
||||
if lhs, ok := got.Underlying().(*types.Basic); ok {
|
||||
return rhs.Info()&types.IsConstType == lhs.Info()&types.IsConstType
|
||||
}
|
||||
}
|
||||
return types.AssignableTo(want, got) || types.ConvertibleTo(want, got)
|
||||
}
|
||||
|
||||
// Error messages have changed across Go versions. These regexps capture recent
|
||||
// incarnations.
|
||||
//
|
||||
// TODO(rfindley): once error codes are exported and exposed via go/packages,
|
||||
// use error codes rather than string matching here.
|
||||
var wrongReturnNumRegexes = []*regexp.Regexp{
|
||||
regexp.MustCompile(`wrong number of return values \(want (\d+), got (\d+)\)`),
|
||||
regexp.MustCompile(`too many return values`),
|
||||
regexp.MustCompile(`not enough return values`),
|
||||
}
|
||||
|
||||
func FixesError(err types.Error) bool {
|
||||
msg := strings.TrimSpace(err.Msg)
|
||||
for _, rx := range wrongReturnNumRegexes {
|
||||
if rx.MatchString(msg) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
// Copyright 2020 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 fillreturns_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
"golang.org/x/tools/gopls/internal/analysis/fillreturns"
|
||||
"golang.org/x/tools/internal/aliases"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
// TODO(golang/go#65294): update expectations and delete this
|
||||
// check once we update go.mod to go1.23 so that
|
||||
// gotypesalias=1 becomes the only supported mode.
|
||||
if aliases.Enabled() {
|
||||
t.Skip("expectations need updating for materialized aliases")
|
||||
}
|
||||
|
||||
testdata := analysistest.TestData()
|
||||
analysistest.RunWithSuggestedFixes(t, testdata, fillreturns.Analyzer, "a", "typeparams")
|
||||
}
|
||||
Vendored
+139
@@ -0,0 +1,139 @@
|
||||
// Copyright 2020 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 fillreturns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"go/ast"
|
||||
ast2 "go/ast"
|
||||
"io"
|
||||
"net/http"
|
||||
. "net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type T struct{}
|
||||
type T1 = T
|
||||
type I interface{}
|
||||
type I1 = I
|
||||
type z func(string, http.Handler) error
|
||||
|
||||
func x() error {
|
||||
return errors.New("foo")
|
||||
}
|
||||
|
||||
// The error messages below changed in 1.18; "return values" covers both forms.
|
||||
|
||||
func b() (string, int, error) {
|
||||
return "", errors.New("foo") // want "return values"
|
||||
}
|
||||
|
||||
func c() (string, int, error) {
|
||||
return 7, errors.New("foo") // want "return values"
|
||||
}
|
||||
|
||||
func d() (string, int, error) {
|
||||
return "", 7 // want "return values"
|
||||
}
|
||||
|
||||
func e() (T, error, *bool) {
|
||||
return (z(http.ListenAndServe))("", nil) // want "return values"
|
||||
}
|
||||
|
||||
func preserveLeft() (int, int, error) {
|
||||
return 1, errors.New("foo") // want "return values"
|
||||
}
|
||||
|
||||
func matchValues() (int, error, string) {
|
||||
return errors.New("foo"), 3 // want "return values"
|
||||
}
|
||||
|
||||
func preventDataOverwrite() (int, string) {
|
||||
return errors.New("foo") // want "return values"
|
||||
}
|
||||
|
||||
func closure() (string, error) {
|
||||
_ = func() (int, error) {
|
||||
return // want "return values"
|
||||
}
|
||||
return // want "return values"
|
||||
}
|
||||
|
||||
func basic() (uint8, uint16, uint32, uint64, int8, int16, int32, int64, float32, float64, complex64, complex128, byte, rune, uint, int, uintptr, string, bool, error) {
|
||||
return // want "return values"
|
||||
}
|
||||
|
||||
func complex() (*int, []int, [2]int, map[int]int) {
|
||||
return // want "return values"
|
||||
}
|
||||
|
||||
func structsAndInterfaces() (T, url.URL, T1, I, I1, io.Reader, Client, ast2.Stmt) {
|
||||
return // want "return values"
|
||||
}
|
||||
|
||||
func m() (int, error) {
|
||||
if 1 == 2 {
|
||||
return // want "return values"
|
||||
} else if 1 == 3 {
|
||||
return errors.New("foo") // want "return values"
|
||||
} else {
|
||||
return 1 // want "return values"
|
||||
}
|
||||
return // want "return values"
|
||||
}
|
||||
|
||||
func convertibleTypes() (ast2.Expr, int) {
|
||||
return &ast2.ArrayType{} // want "return values"
|
||||
}
|
||||
|
||||
func assignableTypes() (map[string]int, int) {
|
||||
type X map[string]int
|
||||
var x X
|
||||
return x // want "return values"
|
||||
}
|
||||
|
||||
func interfaceAndError() (I, int) {
|
||||
return errors.New("foo") // want "return values"
|
||||
}
|
||||
|
||||
func funcOneReturn() (string, error) {
|
||||
return strconv.Itoa(1) // want "return values"
|
||||
}
|
||||
|
||||
func funcMultipleReturn() (int, error, string) {
|
||||
return strconv.Atoi("1")
|
||||
}
|
||||
|
||||
func localFuncMultipleReturn() (string, int, error, string) {
|
||||
return b()
|
||||
}
|
||||
|
||||
func multipleUnused() (int, string, string, string) {
|
||||
return 3, 4, 5 // want "return values"
|
||||
}
|
||||
|
||||
func gotTooMany() int {
|
||||
if true {
|
||||
return 0, "" // want "return values"
|
||||
} else {
|
||||
return 1, 0, nil // want "return values"
|
||||
}
|
||||
return 0, 5, false // want "return values"
|
||||
}
|
||||
|
||||
func fillVars() (int, string, ast.Node, bool, error) {
|
||||
eint := 0
|
||||
s := "a"
|
||||
var t bool
|
||||
if true {
|
||||
err := errors.New("fail")
|
||||
return // want "return values"
|
||||
}
|
||||
n := ast.NewIdent("ident")
|
||||
int := 3
|
||||
var b bool
|
||||
return "" // want "return values"
|
||||
}
|
||||
go/pkg/mod/golang.org/x/tools/gopls@v0.16.2/internal/analysis/fillreturns/testdata/src/a/a.go.golden
Vendored
+139
@@ -0,0 +1,139 @@
|
||||
// Copyright 2020 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 fillreturns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"go/ast"
|
||||
ast2 "go/ast"
|
||||
"io"
|
||||
"net/http"
|
||||
. "net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type T struct{}
|
||||
type T1 = T
|
||||
type I interface{}
|
||||
type I1 = I
|
||||
type z func(string, http.Handler) error
|
||||
|
||||
func x() error {
|
||||
return errors.New("foo")
|
||||
}
|
||||
|
||||
// The error messages below changed in 1.18; "return values" covers both forms.
|
||||
|
||||
func b() (string, int, error) {
|
||||
return "", 0, errors.New("foo") // want "return values"
|
||||
}
|
||||
|
||||
func c() (string, int, error) {
|
||||
return "", 7, errors.New("foo") // want "return values"
|
||||
}
|
||||
|
||||
func d() (string, int, error) {
|
||||
return "", 7, nil // want "return values"
|
||||
}
|
||||
|
||||
func e() (T, error, *bool) {
|
||||
return T{}, (z(http.ListenAndServe))("", nil), nil // want "return values"
|
||||
}
|
||||
|
||||
func preserveLeft() (int, int, error) {
|
||||
return 1, 0, errors.New("foo") // want "return values"
|
||||
}
|
||||
|
||||
func matchValues() (int, error, string) {
|
||||
return 3, errors.New("foo"), "" // want "return values"
|
||||
}
|
||||
|
||||
func preventDataOverwrite() (int, string) {
|
||||
return 0, "", errors.New("foo") // want "return values"
|
||||
}
|
||||
|
||||
func closure() (string, error) {
|
||||
_ = func() (int, error) {
|
||||
return 0, nil // want "return values"
|
||||
}
|
||||
return "", nil // want "return values"
|
||||
}
|
||||
|
||||
func basic() (uint8, uint16, uint32, uint64, int8, int16, int32, int64, float32, float64, complex64, complex128, byte, rune, uint, int, uintptr, string, bool, error) {
|
||||
return 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", false, nil // want "return values"
|
||||
}
|
||||
|
||||
func complex() (*int, []int, [2]int, map[int]int) {
|
||||
return nil, nil, nil, nil // want "return values"
|
||||
}
|
||||
|
||||
func structsAndInterfaces() (T, url.URL, T1, I, I1, io.Reader, Client, ast2.Stmt) {
|
||||
return T{}, url.URL{}, T{}, nil, nil, nil, Client{}, nil // want "return values"
|
||||
}
|
||||
|
||||
func m() (int, error) {
|
||||
if 1 == 2 {
|
||||
return 0, nil // want "return values"
|
||||
} else if 1 == 3 {
|
||||
return 0, errors.New("foo") // want "return values"
|
||||
} else {
|
||||
return 1, nil // want "return values"
|
||||
}
|
||||
return 0, nil // want "return values"
|
||||
}
|
||||
|
||||
func convertibleTypes() (ast2.Expr, int) {
|
||||
return &ast2.ArrayType{}, 0 // want "return values"
|
||||
}
|
||||
|
||||
func assignableTypes() (map[string]int, int) {
|
||||
type X map[string]int
|
||||
var x X
|
||||
return x, 0 // want "return values"
|
||||
}
|
||||
|
||||
func interfaceAndError() (I, int) {
|
||||
return errors.New("foo"), 0 // want "return values"
|
||||
}
|
||||
|
||||
func funcOneReturn() (string, error) {
|
||||
return strconv.Itoa(1), nil // want "return values"
|
||||
}
|
||||
|
||||
func funcMultipleReturn() (int, error, string) {
|
||||
return strconv.Atoi("1")
|
||||
}
|
||||
|
||||
func localFuncMultipleReturn() (string, int, error, string) {
|
||||
return b()
|
||||
}
|
||||
|
||||
func multipleUnused() (int, string, string, string) {
|
||||
return 3, "", "", "", 4, 5 // want "return values"
|
||||
}
|
||||
|
||||
func gotTooMany() int {
|
||||
if true {
|
||||
return 0 // want "return values"
|
||||
} else {
|
||||
return 1 // want "return values"
|
||||
}
|
||||
return 5 // want "return values"
|
||||
}
|
||||
|
||||
func fillVars() (int, string, ast.Node, bool, error) {
|
||||
eint := 0
|
||||
s := "a"
|
||||
var t bool
|
||||
if true {
|
||||
err := errors.New("fail")
|
||||
return eint, s, nil, false, err // want "return values"
|
||||
}
|
||||
n := ast.NewIdent("ident")
|
||||
int := 3
|
||||
var b bool
|
||||
return int, "", n, b, nil // want "return values"
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package fillreturns
|
||||
|
||||
func hello[T any]() int {
|
||||
return
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package fillreturns
|
||||
|
||||
func hello[T any]() int {
|
||||
return
|
||||
}
|
||||
+497
@@ -0,0 +1,497 @@
|
||||
// Copyright 2020 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 fillstruct defines an Analyzer that automatically
|
||||
// fills in a struct declaration with zero value elements for each field.
|
||||
//
|
||||
// The analyzer's diagnostic is merely a prompt.
|
||||
// The actual fix is created by a separate direct call from gopls to
|
||||
// the SuggestedFixes function.
|
||||
// Tests of Analyzer.Run can be found in ./testdata/src.
|
||||
// Tests of the SuggestedFixes logic live in ../../testdata/fillstruct.
|
||||
package fillstruct
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/gopls/internal/util/safetoken"
|
||||
"golang.org/x/tools/internal/aliases"
|
||||
"golang.org/x/tools/internal/analysisinternal"
|
||||
"golang.org/x/tools/internal/fuzzy"
|
||||
"golang.org/x/tools/internal/typeparams"
|
||||
)
|
||||
|
||||
// Diagnose computes diagnostics for fillable struct literals overlapping with
|
||||
// the provided start and end position of file f.
|
||||
//
|
||||
// The diagnostic contains a lazy fix; the actual patch is computed
|
||||
// (via the ApplyFix command) by a call to [SuggestedFix].
|
||||
//
|
||||
// If either start or end is invalid, the entire file is inspected.
|
||||
func Diagnose(f *ast.File, start, end token.Pos, pkg *types.Package, info *types.Info) []analysis.Diagnostic {
|
||||
var diags []analysis.Diagnostic
|
||||
ast.Inspect(f, func(n ast.Node) bool {
|
||||
if n == nil {
|
||||
return true // pop
|
||||
}
|
||||
if start.IsValid() && n.End() < start || end.IsValid() && n.Pos() > end {
|
||||
return false // skip non-overlapping subtree
|
||||
}
|
||||
expr, ok := n.(*ast.CompositeLit)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
typ := info.TypeOf(expr)
|
||||
if typ == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
// Find reference to the type declaration of the struct being initialized.
|
||||
typ = typeparams.Deref(typ)
|
||||
tStruct, ok := typeparams.CoreType(typ).(*types.Struct)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
// Inv: typ is the possibly-named struct type.
|
||||
|
||||
fieldCount := tStruct.NumFields()
|
||||
|
||||
// Skip any struct that is already populated or that has no fields.
|
||||
if fieldCount == 0 || fieldCount == len(expr.Elts) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Are any fields in need of filling?
|
||||
var fillableFields []string
|
||||
for i := 0; i < fieldCount; i++ {
|
||||
field := tStruct.Field(i)
|
||||
// Ignore fields that are not accessible in the current package.
|
||||
if field.Pkg() != nil && field.Pkg() != pkg && !field.Exported() {
|
||||
continue
|
||||
}
|
||||
fillableFields = append(fillableFields, fmt.Sprintf("%s: %s", field.Name(), field.Type().String()))
|
||||
}
|
||||
if len(fillableFields) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Derive a name for the struct type.
|
||||
var name string
|
||||
if typ != tStruct {
|
||||
// named struct type (e.g. pkg.S[T])
|
||||
name = types.TypeString(typ, types.RelativeTo(pkg))
|
||||
} else {
|
||||
// anonymous struct type
|
||||
totalFields := len(fillableFields)
|
||||
const maxLen = 20
|
||||
// Find the index to cut off printing of fields.
|
||||
var i, fieldLen int
|
||||
for i = range fillableFields {
|
||||
if fieldLen > maxLen {
|
||||
break
|
||||
}
|
||||
fieldLen += len(fillableFields[i])
|
||||
}
|
||||
fillableFields = fillableFields[:i]
|
||||
if i < totalFields {
|
||||
fillableFields = append(fillableFields, "...")
|
||||
}
|
||||
name = fmt.Sprintf("anonymous struct{ %s }", strings.Join(fillableFields, ", "))
|
||||
}
|
||||
diags = append(diags, analysis.Diagnostic{
|
||||
Message: fmt.Sprintf("%s literal has missing fields", name),
|
||||
Pos: expr.Pos(),
|
||||
End: expr.End(),
|
||||
Category: FixCategory,
|
||||
SuggestedFixes: []analysis.SuggestedFix{{
|
||||
Message: fmt.Sprintf("Fill %s", name),
|
||||
// No TextEdits => computed later by gopls.
|
||||
}},
|
||||
})
|
||||
return true
|
||||
})
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
const FixCategory = "fillstruct" // recognized by gopls ApplyFix
|
||||
|
||||
// SuggestedFix computes the suggested fix for the kinds of
|
||||
// diagnostics produced by the Analyzer above.
|
||||
func SuggestedFix(fset *token.FileSet, start, end token.Pos, content []byte, file *ast.File, pkg *types.Package, info *types.Info) (*token.FileSet, *analysis.SuggestedFix, error) {
|
||||
if info == nil {
|
||||
return nil, nil, fmt.Errorf("nil types.Info")
|
||||
}
|
||||
|
||||
pos := start // don't use the end
|
||||
|
||||
// TODO(rstambler): Using ast.Inspect would probably be more efficient than
|
||||
// calling PathEnclosingInterval. Switch this approach.
|
||||
path, _ := astutil.PathEnclosingInterval(file, pos, pos)
|
||||
if len(path) == 0 {
|
||||
return nil, nil, fmt.Errorf("no enclosing ast.Node")
|
||||
}
|
||||
var expr *ast.CompositeLit
|
||||
for _, n := range path {
|
||||
if node, ok := n.(*ast.CompositeLit); ok {
|
||||
expr = node
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
typ := info.TypeOf(expr)
|
||||
if typ == nil {
|
||||
return nil, nil, fmt.Errorf("no composite literal")
|
||||
}
|
||||
|
||||
// Find reference to the type declaration of the struct being initialized.
|
||||
typ = typeparams.Deref(typ)
|
||||
tStruct, ok := typ.Underlying().(*types.Struct)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("%s is not a (pointer to) struct type",
|
||||
types.TypeString(typ, types.RelativeTo(pkg)))
|
||||
}
|
||||
// Inv: typ is the possibly-named struct type.
|
||||
|
||||
fieldCount := tStruct.NumFields()
|
||||
|
||||
// Check which types have already been filled in. (we only want to fill in
|
||||
// the unfilled types, or else we'll blat user-supplied details)
|
||||
prefilledFields := map[string]ast.Expr{}
|
||||
for _, e := range expr.Elts {
|
||||
if kv, ok := e.(*ast.KeyValueExpr); ok {
|
||||
if key, ok := kv.Key.(*ast.Ident); ok {
|
||||
prefilledFields[key.Name] = kv.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use a new fileset to build up a token.File for the new composite
|
||||
// literal. We need one line for foo{, one line for }, and one line for
|
||||
// each field we're going to set. format.Node only cares about line
|
||||
// numbers, so we don't need to set columns, and each line can be
|
||||
// 1 byte long.
|
||||
// TODO(adonovan): why is this necessary? The position information
|
||||
// is going to be wrong for the existing trees in prefilledFields.
|
||||
// Can't the formatter just do its best with an empty fileset?
|
||||
fakeFset := token.NewFileSet()
|
||||
tok := fakeFset.AddFile("", -1, fieldCount+2)
|
||||
|
||||
line := 2 // account for 1-based lines and the left brace
|
||||
var fieldTyps []types.Type
|
||||
for i := 0; i < fieldCount; i++ {
|
||||
field := tStruct.Field(i)
|
||||
// Ignore fields that are not accessible in the current package.
|
||||
if field.Pkg() != nil && field.Pkg() != pkg && !field.Exported() {
|
||||
fieldTyps = append(fieldTyps, nil)
|
||||
continue
|
||||
}
|
||||
fieldTyps = append(fieldTyps, field.Type())
|
||||
}
|
||||
matches := analysisinternal.MatchingIdents(fieldTyps, file, start, info, pkg)
|
||||
var elts []ast.Expr
|
||||
for i, fieldTyp := range fieldTyps {
|
||||
if fieldTyp == nil {
|
||||
continue // TODO(adonovan): is this reachable?
|
||||
}
|
||||
fieldName := tStruct.Field(i).Name()
|
||||
|
||||
tok.AddLine(line - 1) // add 1 byte per line
|
||||
if line > tok.LineCount() {
|
||||
panic(fmt.Sprintf("invalid line number %v (of %v) for fillstruct", line, tok.LineCount()))
|
||||
}
|
||||
pos := tok.LineStart(line)
|
||||
|
||||
kv := &ast.KeyValueExpr{
|
||||
Key: &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: fieldName,
|
||||
},
|
||||
Colon: pos,
|
||||
}
|
||||
if expr, ok := prefilledFields[fieldName]; ok {
|
||||
kv.Value = expr
|
||||
} else {
|
||||
names, ok := matches[fieldTyp]
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("invalid struct field type: %v", fieldTyp)
|
||||
}
|
||||
|
||||
// Find the name most similar to the field name.
|
||||
// If no name matches the pattern, generate a zero value.
|
||||
// NOTE: We currently match on the name of the field key rather than the field type.
|
||||
if best := fuzzy.BestMatch(fieldName, names); best != "" {
|
||||
kv.Value = ast.NewIdent(best)
|
||||
} else if v := populateValue(file, pkg, fieldTyp); v != nil {
|
||||
kv.Value = v
|
||||
} else {
|
||||
return nil, nil, nil // no fix to suggest
|
||||
}
|
||||
}
|
||||
elts = append(elts, kv)
|
||||
line++
|
||||
}
|
||||
|
||||
// If all of the struct's fields are unexported, we have nothing to do.
|
||||
if len(elts) == 0 {
|
||||
return nil, nil, fmt.Errorf("no elements to fill")
|
||||
}
|
||||
|
||||
// Add the final line for the right brace. Offset is the number of
|
||||
// bytes already added plus 1.
|
||||
tok.AddLine(len(elts) + 1)
|
||||
line = len(elts) + 2
|
||||
if line > tok.LineCount() {
|
||||
panic(fmt.Sprintf("invalid line number %v (of %v) for fillstruct", line, tok.LineCount()))
|
||||
}
|
||||
|
||||
cl := &ast.CompositeLit{
|
||||
Type: expr.Type,
|
||||
Lbrace: tok.LineStart(1),
|
||||
Elts: elts,
|
||||
Rbrace: tok.LineStart(line),
|
||||
}
|
||||
|
||||
// Find the line on which the composite literal is declared.
|
||||
split := bytes.Split(content, []byte("\n"))
|
||||
lineNumber := safetoken.StartPosition(fset, expr.Lbrace).Line
|
||||
firstLine := split[lineNumber-1] // lines are 1-indexed
|
||||
|
||||
// Trim the whitespace from the left of the line, and use the index
|
||||
// to get the amount of whitespace on the left.
|
||||
trimmed := bytes.TrimLeftFunc(firstLine, unicode.IsSpace)
|
||||
index := bytes.Index(firstLine, trimmed)
|
||||
whitespace := firstLine[:index]
|
||||
|
||||
// First pass through the formatter: turn the expr into a string.
|
||||
var formatBuf bytes.Buffer
|
||||
if err := format.Node(&formatBuf, fakeFset, cl); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to run first format on:\n%s\ngot err: %v", cl.Type, err)
|
||||
}
|
||||
sug := indent(formatBuf.Bytes(), whitespace)
|
||||
|
||||
if len(prefilledFields) > 0 {
|
||||
// Attempt a second pass through the formatter to line up columns.
|
||||
sourced, err := format.Source(sug)
|
||||
if err == nil {
|
||||
sug = indent(sourced, whitespace)
|
||||
}
|
||||
}
|
||||
|
||||
return fset, &analysis.SuggestedFix{
|
||||
TextEdits: []analysis.TextEdit{
|
||||
{
|
||||
Pos: expr.Pos(),
|
||||
End: expr.End(),
|
||||
NewText: sug,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// indent works line by line through str, indenting (prefixing) each line with
|
||||
// ind.
|
||||
func indent(str, ind []byte) []byte {
|
||||
split := bytes.Split(str, []byte("\n"))
|
||||
newText := bytes.NewBuffer(nil)
|
||||
for i, s := range split {
|
||||
if len(s) == 0 {
|
||||
continue
|
||||
}
|
||||
// Don't add the extra indentation to the first line.
|
||||
if i != 0 {
|
||||
newText.Write(ind)
|
||||
}
|
||||
newText.Write(s)
|
||||
if i < len(split)-1 {
|
||||
newText.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
return newText.Bytes()
|
||||
}
|
||||
|
||||
// populateValue constructs an expression to fill the value of a struct field.
|
||||
//
|
||||
// When the type of a struct field is a basic literal or interface, we return
|
||||
// default values. For other types, such as maps, slices, and channels, we create
|
||||
// empty expressions such as []T{} or make(chan T) rather than using default values.
|
||||
//
|
||||
// The reasoning here is that users will call fillstruct with the intention of
|
||||
// initializing the struct, in which case setting these fields to nil has no effect.
|
||||
//
|
||||
// populateValue returns nil if the value cannot be filled.
|
||||
func populateValue(f *ast.File, pkg *types.Package, typ types.Type) ast.Expr {
|
||||
switch u := typ.Underlying().(type) {
|
||||
case *types.Basic:
|
||||
switch {
|
||||
case u.Info()&types.IsNumeric != 0:
|
||||
return &ast.BasicLit{Kind: token.INT, Value: "0"}
|
||||
case u.Info()&types.IsBoolean != 0:
|
||||
return &ast.Ident{Name: "false"}
|
||||
case u.Info()&types.IsString != 0:
|
||||
return &ast.BasicLit{Kind: token.STRING, Value: `""`}
|
||||
case u.Kind() == types.UnsafePointer:
|
||||
return ast.NewIdent("nil")
|
||||
case u.Kind() == types.Invalid:
|
||||
return nil
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown basic type %v", u))
|
||||
}
|
||||
|
||||
case *types.Map:
|
||||
k := analysisinternal.TypeExpr(f, pkg, u.Key())
|
||||
v := analysisinternal.TypeExpr(f, pkg, u.Elem())
|
||||
if k == nil || v == nil {
|
||||
return nil
|
||||
}
|
||||
return &ast.CompositeLit{
|
||||
Type: &ast.MapType{
|
||||
Key: k,
|
||||
Value: v,
|
||||
},
|
||||
}
|
||||
case *types.Slice:
|
||||
s := analysisinternal.TypeExpr(f, pkg, u.Elem())
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
return &ast.CompositeLit{
|
||||
Type: &ast.ArrayType{
|
||||
Elt: s,
|
||||
},
|
||||
}
|
||||
|
||||
case *types.Array:
|
||||
a := analysisinternal.TypeExpr(f, pkg, u.Elem())
|
||||
if a == nil {
|
||||
return nil
|
||||
}
|
||||
return &ast.CompositeLit{
|
||||
Type: &ast.ArrayType{
|
||||
Elt: a,
|
||||
Len: &ast.BasicLit{
|
||||
Kind: token.INT, Value: fmt.Sprintf("%v", u.Len()),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
case *types.Chan:
|
||||
v := analysisinternal.TypeExpr(f, pkg, u.Elem())
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
dir := ast.ChanDir(u.Dir())
|
||||
if u.Dir() == types.SendRecv {
|
||||
dir = ast.SEND | ast.RECV
|
||||
}
|
||||
return &ast.CallExpr{
|
||||
Fun: ast.NewIdent("make"),
|
||||
Args: []ast.Expr{
|
||||
&ast.ChanType{
|
||||
Dir: dir,
|
||||
Value: v,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
case *types.Struct:
|
||||
s := analysisinternal.TypeExpr(f, pkg, typ)
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
return &ast.CompositeLit{
|
||||
Type: s,
|
||||
}
|
||||
|
||||
case *types.Signature:
|
||||
var params []*ast.Field
|
||||
for i := 0; i < u.Params().Len(); i++ {
|
||||
p := analysisinternal.TypeExpr(f, pkg, u.Params().At(i).Type())
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
params = append(params, &ast.Field{
|
||||
Type: p,
|
||||
Names: []*ast.Ident{
|
||||
{
|
||||
Name: u.Params().At(i).Name(),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
var returns []*ast.Field
|
||||
for i := 0; i < u.Results().Len(); i++ {
|
||||
r := analysisinternal.TypeExpr(f, pkg, u.Results().At(i).Type())
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
returns = append(returns, &ast.Field{
|
||||
Type: r,
|
||||
})
|
||||
}
|
||||
return &ast.FuncLit{
|
||||
Type: &ast.FuncType{
|
||||
Params: &ast.FieldList{
|
||||
List: params,
|
||||
},
|
||||
Results: &ast.FieldList{
|
||||
List: returns,
|
||||
},
|
||||
},
|
||||
Body: &ast.BlockStmt{},
|
||||
}
|
||||
|
||||
case *types.Pointer:
|
||||
switch aliases.Unalias(u.Elem()).(type) {
|
||||
case *types.Basic:
|
||||
return &ast.CallExpr{
|
||||
Fun: &ast.Ident{
|
||||
Name: "new",
|
||||
},
|
||||
Args: []ast.Expr{
|
||||
&ast.Ident{
|
||||
Name: u.Elem().String(),
|
||||
},
|
||||
},
|
||||
}
|
||||
default:
|
||||
x := populateValue(f, pkg, u.Elem())
|
||||
if x == nil {
|
||||
return nil
|
||||
}
|
||||
return &ast.UnaryExpr{
|
||||
Op: token.AND,
|
||||
X: x,
|
||||
}
|
||||
}
|
||||
|
||||
case *types.Interface:
|
||||
if param, ok := aliases.Unalias(typ).(*types.TypeParam); ok {
|
||||
// *new(T) is the zero value of a type parameter T.
|
||||
// TODO(adonovan): one could give a more specific zero
|
||||
// value if the type has a core type that is, say,
|
||||
// always a number or a pointer. See go/ssa for details.
|
||||
return &ast.StarExpr{
|
||||
X: &ast.CallExpr{
|
||||
Fun: ast.NewIdent("new"),
|
||||
Args: []ast.Expr{
|
||||
ast.NewIdent(param.Obj().Name()),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return ast.NewIdent("nil")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// Copyright 2020 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 fillstruct_test
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
"golang.org/x/tools/gopls/internal/analysis/fillstruct"
|
||||
)
|
||||
|
||||
// analyzer allows us to test the fillstruct code action using the analysistest
|
||||
// harness. (fillstruct used to be a gopls analyzer.)
|
||||
var analyzer = &analysis.Analyzer{
|
||||
Name: "fillstruct",
|
||||
Doc: "test only",
|
||||
Run: func(pass *analysis.Pass) (any, error) {
|
||||
for _, f := range pass.Files {
|
||||
for _, diag := range fillstruct.Diagnose(f, token.NoPos, token.NoPos, pass.Pkg, pass.TypesInfo) {
|
||||
pass.Report(diag)
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
},
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/fillstruct",
|
||||
RunDespiteErrors: true,
|
||||
}
|
||||
|
||||
func Test(t *testing.T) {
|
||||
testdata := analysistest.TestData()
|
||||
analysistest.Run(t, testdata, analyzer, "a", "typeparams")
|
||||
}
|
||||
Vendored
+112
@@ -0,0 +1,112 @@
|
||||
// Copyright 2020 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 fillstruct
|
||||
|
||||
import (
|
||||
data "b"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type emptyStruct struct{}
|
||||
|
||||
var _ = emptyStruct{}
|
||||
|
||||
type basicStruct struct {
|
||||
foo int
|
||||
}
|
||||
|
||||
var _ = basicStruct{} // want `basicStruct literal has missing fields`
|
||||
|
||||
type twoArgStruct struct {
|
||||
foo int
|
||||
bar string
|
||||
}
|
||||
|
||||
var _ = twoArgStruct{} // want `twoArgStruct literal has missing fields`
|
||||
|
||||
var _ = twoArgStruct{ // want `twoArgStruct literal has missing fields`
|
||||
bar: "bar",
|
||||
}
|
||||
|
||||
type nestedStruct struct {
|
||||
bar string
|
||||
basic basicStruct
|
||||
}
|
||||
|
||||
var _ = nestedStruct{} // want `nestedStruct literal has missing fields`
|
||||
|
||||
var _ = data.B{} // want `b.B literal has missing fields`
|
||||
|
||||
type typedStruct struct {
|
||||
m map[string]int
|
||||
s []int
|
||||
c chan int
|
||||
c1 <-chan int
|
||||
a [2]string
|
||||
}
|
||||
|
||||
var _ = typedStruct{} // want `typedStruct literal has missing fields`
|
||||
|
||||
type funStruct struct {
|
||||
fn func(i int) int
|
||||
}
|
||||
|
||||
var _ = funStruct{} // want `funStruct literal has missing fields`
|
||||
|
||||
type funStructComplex struct {
|
||||
fn func(i int, s string) (string, int)
|
||||
}
|
||||
|
||||
var _ = funStructComplex{} // want `funStructComplex literal has missing fields`
|
||||
|
||||
type funStructEmpty struct {
|
||||
fn func()
|
||||
}
|
||||
|
||||
var _ = funStructEmpty{} // want `funStructEmpty literal has missing fields`
|
||||
|
||||
type Foo struct {
|
||||
A int
|
||||
}
|
||||
|
||||
type Bar struct {
|
||||
X *Foo
|
||||
Y *Foo
|
||||
}
|
||||
|
||||
var _ = Bar{} // want `Bar literal has missing fields`
|
||||
|
||||
type importedStruct struct {
|
||||
m map[*ast.CompositeLit]ast.Field
|
||||
s []ast.BadExpr
|
||||
a [3]token.Token
|
||||
c chan ast.EmptyStmt
|
||||
fn func(ast_decl ast.DeclStmt) ast.Ellipsis
|
||||
st ast.CompositeLit
|
||||
}
|
||||
|
||||
var _ = importedStruct{} // want `importedStruct literal has missing fields`
|
||||
|
||||
type pointerBuiltinStruct struct {
|
||||
b *bool
|
||||
s *string
|
||||
i *int
|
||||
}
|
||||
|
||||
var _ = pointerBuiltinStruct{} // want `pointerBuiltinStruct literal has missing fields`
|
||||
|
||||
var _ = []ast.BasicLit{
|
||||
{}, // want `go/ast.BasicLit literal has missing fields`
|
||||
}
|
||||
|
||||
var _ = []ast.BasicLit{{}} // want "go/ast.BasicLit literal has missing fields"
|
||||
|
||||
type unsafeStruct struct {
|
||||
foo unsafe.Pointer
|
||||
}
|
||||
|
||||
var _ = unsafeStruct{} // want `unsafeStruct literal has missing fields`
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
package fillstruct
|
||||
|
||||
type B struct {
|
||||
ExportedInt int
|
||||
unexportedInt int
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
// Copyright 2020 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 fillstruct
|
||||
|
||||
type emptyStruct[A any] struct{}
|
||||
|
||||
var _ = emptyStruct[int]{}
|
||||
|
||||
type basicStruct[T any] struct {
|
||||
foo T
|
||||
}
|
||||
|
||||
var _ = basicStruct[int]{} // want `basicStruct\[int\] literal has missing fields`
|
||||
|
||||
type twoArgStruct[F, B any] struct {
|
||||
foo F
|
||||
bar B
|
||||
}
|
||||
|
||||
var _ = twoArgStruct[string, int]{} // want `twoArgStruct\[string, int\] literal has missing fields`
|
||||
|
||||
var _ = twoArgStruct[int, string]{ // want `twoArgStruct\[int, string\] literal has missing fields`
|
||||
bar: "bar",
|
||||
}
|
||||
|
||||
type nestedStruct struct {
|
||||
bar string
|
||||
basic basicStruct[int]
|
||||
}
|
||||
|
||||
var _ = nestedStruct{} // want "nestedStruct literal has missing fields"
|
||||
|
||||
func _[T any]() {
|
||||
type S struct{ t T }
|
||||
x := S{} // want "S"
|
||||
_ = x
|
||||
}
|
||||
|
||||
func Test() {
|
||||
var tests = []struct {
|
||||
a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p string
|
||||
}{
|
||||
{}, // want "anonymous struct{ a: string, b: string, c: string, ... } literal has missing fields"
|
||||
}
|
||||
for _, test := range tests {
|
||||
_ = test
|
||||
}
|
||||
}
|
||||
|
||||
func _[T twoArgStruct[int, int]]() {
|
||||
_ = T{} // want "T literal has missing fields"
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2024 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 fillswitch identifies switches with missing cases.
|
||||
//
|
||||
// It reports a diagnostic for each type switch or 'enum' switch that
|
||||
// has missing cases, and suggests a fix to fill them in.
|
||||
//
|
||||
// The possible cases are: for a type switch, each accessible named
|
||||
// type T or pointer *T that is assignable to the interface type; and
|
||||
// for an 'enum' switch, each accessible named constant of the same
|
||||
// type as the switch value.
|
||||
//
|
||||
// For an 'enum' switch, it will suggest cases for all possible values of the
|
||||
// type.
|
||||
//
|
||||
// type Suit int8
|
||||
// const (
|
||||
// Spades Suit = iota
|
||||
// Hearts
|
||||
// Diamonds
|
||||
// Clubs
|
||||
// )
|
||||
//
|
||||
// var s Suit
|
||||
// switch s {
|
||||
// case Spades:
|
||||
// }
|
||||
//
|
||||
// It will report a diagnostic with a suggested fix to fill in the remaining
|
||||
// cases:
|
||||
//
|
||||
// var s Suit
|
||||
// switch s {
|
||||
// case Spades:
|
||||
// case Hearts:
|
||||
// case Diamonds:
|
||||
// case Clubs:
|
||||
// default:
|
||||
// panic(fmt.Sprintf("unexpected Suit: %v", s))
|
||||
// }
|
||||
//
|
||||
// For a type switch, it will suggest cases for all types that implement the
|
||||
// interface.
|
||||
//
|
||||
// var stmt ast.Stmt
|
||||
// switch stmt.(type) {
|
||||
// case *ast.IfStmt:
|
||||
// }
|
||||
//
|
||||
// It will report a diagnostic with a suggested fix to fill in the remaining
|
||||
// cases:
|
||||
//
|
||||
// var stmt ast.Stmt
|
||||
// switch stmt.(type) {
|
||||
// case *ast.IfStmt:
|
||||
// case *ast.ForStmt:
|
||||
// case *ast.RangeStmt:
|
||||
// case *ast.AssignStmt:
|
||||
// case *ast.GoStmt:
|
||||
// ...
|
||||
// default:
|
||||
// panic(fmt.Sprintf("unexpected ast.Stmt: %T", stmt))
|
||||
// }
|
||||
package fillswitch
|
||||
+299
@@ -0,0 +1,299 @@
|
||||
// Copyright 2024 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 fillswitch
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
// Diagnose computes diagnostics for switch statements with missing cases
|
||||
// overlapping with the provided start and end position of file f.
|
||||
//
|
||||
// If either start or end is invalid, the entire file is inspected.
|
||||
func Diagnose(f *ast.File, start, end token.Pos, pkg *types.Package, info *types.Info) []analysis.Diagnostic {
|
||||
var diags []analysis.Diagnostic
|
||||
ast.Inspect(f, func(n ast.Node) bool {
|
||||
if n == nil {
|
||||
return true // pop
|
||||
}
|
||||
if start.IsValid() && n.End() < start ||
|
||||
end.IsValid() && n.Pos() > end {
|
||||
return false // skip non-overlapping subtree
|
||||
}
|
||||
var fix *analysis.SuggestedFix
|
||||
switch n := n.(type) {
|
||||
case *ast.SwitchStmt:
|
||||
fix = suggestedFixSwitch(n, pkg, info)
|
||||
case *ast.TypeSwitchStmt:
|
||||
fix = suggestedFixTypeSwitch(n, pkg, info)
|
||||
}
|
||||
if fix != nil {
|
||||
diags = append(diags, analysis.Diagnostic{
|
||||
Message: fix.Message,
|
||||
Pos: n.Pos(),
|
||||
End: n.Pos() + token.Pos(len("switch")),
|
||||
SuggestedFixes: []analysis.SuggestedFix{*fix},
|
||||
})
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
func suggestedFixTypeSwitch(stmt *ast.TypeSwitchStmt, pkg *types.Package, info *types.Info) *analysis.SuggestedFix {
|
||||
if hasDefaultCase(stmt.Body) {
|
||||
return nil
|
||||
}
|
||||
|
||||
namedType := namedTypeFromTypeSwitch(stmt, info)
|
||||
if namedType == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
existingCases := caseTypes(stmt.Body, info)
|
||||
// Gather accessible package-level concrete types
|
||||
// that implement the switch interface type.
|
||||
scope := namedType.Obj().Pkg().Scope()
|
||||
var buf bytes.Buffer
|
||||
for _, name := range scope.Names() {
|
||||
obj := scope.Lookup(name)
|
||||
if tname, ok := obj.(*types.TypeName); !ok || tname.IsAlias() {
|
||||
continue // not a defined type
|
||||
}
|
||||
|
||||
if types.IsInterface(obj.Type()) {
|
||||
continue
|
||||
}
|
||||
|
||||
samePkg := obj.Pkg() == pkg
|
||||
if !samePkg && !obj.Exported() {
|
||||
continue // inaccessible
|
||||
}
|
||||
|
||||
var key caseType
|
||||
if types.AssignableTo(obj.Type(), namedType.Obj().Type()) {
|
||||
key.named = obj.Type().(*types.Named)
|
||||
} else if ptr := types.NewPointer(obj.Type()); types.AssignableTo(ptr, namedType.Obj().Type()) {
|
||||
key.named = obj.Type().(*types.Named)
|
||||
key.ptr = true
|
||||
}
|
||||
|
||||
if key.named != nil {
|
||||
if existingCases[key] {
|
||||
continue
|
||||
}
|
||||
|
||||
if buf.Len() > 0 {
|
||||
buf.WriteString("\t")
|
||||
}
|
||||
|
||||
buf.WriteString("case ")
|
||||
if key.ptr {
|
||||
buf.WriteByte('*')
|
||||
}
|
||||
|
||||
if p := key.named.Obj().Pkg(); p != pkg {
|
||||
// TODO: use the correct package name when the import is renamed
|
||||
buf.WriteString(p.Name())
|
||||
buf.WriteByte('.')
|
||||
}
|
||||
buf.WriteString(key.named.Obj().Name())
|
||||
buf.WriteString(":\n")
|
||||
}
|
||||
}
|
||||
|
||||
if buf.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch assign := stmt.Assign.(type) {
|
||||
case *ast.AssignStmt:
|
||||
addDefaultCase(&buf, namedType, assign.Lhs[0])
|
||||
case *ast.ExprStmt:
|
||||
if assert, ok := assign.X.(*ast.TypeAssertExpr); ok {
|
||||
addDefaultCase(&buf, namedType, assert.X)
|
||||
}
|
||||
}
|
||||
|
||||
return &analysis.SuggestedFix{
|
||||
Message: fmt.Sprintf("Add cases for %s", namedType.Obj().Name()),
|
||||
TextEdits: []analysis.TextEdit{{
|
||||
Pos: stmt.End() - token.Pos(len("}")),
|
||||
End: stmt.End() - token.Pos(len("}")),
|
||||
NewText: buf.Bytes(),
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
func suggestedFixSwitch(stmt *ast.SwitchStmt, pkg *types.Package, info *types.Info) *analysis.SuggestedFix {
|
||||
if hasDefaultCase(stmt.Body) {
|
||||
return nil
|
||||
}
|
||||
|
||||
namedType, ok := info.TypeOf(stmt.Tag).(*types.Named)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
existingCases := caseConsts(stmt.Body, info)
|
||||
// Gather accessible named constants of the same type as the switch value.
|
||||
scope := namedType.Obj().Pkg().Scope()
|
||||
var buf bytes.Buffer
|
||||
for _, name := range scope.Names() {
|
||||
obj := scope.Lookup(name)
|
||||
if c, ok := obj.(*types.Const); ok &&
|
||||
(obj.Pkg() == pkg || obj.Exported()) && // accessible
|
||||
types.Identical(obj.Type(), namedType.Obj().Type()) &&
|
||||
!existingCases[c] {
|
||||
|
||||
if buf.Len() > 0 {
|
||||
buf.WriteString("\t")
|
||||
}
|
||||
|
||||
buf.WriteString("case ")
|
||||
if c.Pkg() != pkg {
|
||||
buf.WriteString(c.Pkg().Name())
|
||||
buf.WriteByte('.')
|
||||
}
|
||||
buf.WriteString(c.Name())
|
||||
buf.WriteString(":\n")
|
||||
}
|
||||
}
|
||||
|
||||
if buf.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
addDefaultCase(&buf, namedType, stmt.Tag)
|
||||
|
||||
return &analysis.SuggestedFix{
|
||||
Message: fmt.Sprintf("Add cases for %s", namedType.Obj().Name()),
|
||||
TextEdits: []analysis.TextEdit{{
|
||||
Pos: stmt.End() - token.Pos(len("}")),
|
||||
End: stmt.End() - token.Pos(len("}")),
|
||||
NewText: buf.Bytes(),
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
func addDefaultCase(buf *bytes.Buffer, named *types.Named, expr ast.Expr) {
|
||||
var dottedBuf bytes.Buffer
|
||||
// writeDotted emits a dotted path a.b.c.
|
||||
var writeDotted func(e ast.Expr) bool
|
||||
writeDotted = func(e ast.Expr) bool {
|
||||
switch e := e.(type) {
|
||||
case *ast.SelectorExpr:
|
||||
if !writeDotted(e.X) {
|
||||
return false
|
||||
}
|
||||
dottedBuf.WriteByte('.')
|
||||
dottedBuf.WriteString(e.Sel.Name)
|
||||
return true
|
||||
case *ast.Ident:
|
||||
dottedBuf.WriteString(e.Name)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
buf.WriteString("\tdefault:\n")
|
||||
typeName := fmt.Sprintf("%s.%s", named.Obj().Pkg().Name(), named.Obj().Name())
|
||||
if writeDotted(expr) {
|
||||
// Switch tag expression is a dotted path.
|
||||
// It is safe to re-evaluate it in the default case.
|
||||
format := fmt.Sprintf("unexpected %s: %%#v", typeName)
|
||||
fmt.Fprintf(buf, "\t\tpanic(fmt.Sprintf(%q, %s))\n\t", format, dottedBuf.String())
|
||||
} else {
|
||||
// Emit simpler message, without re-evaluating tag expression.
|
||||
fmt.Fprintf(buf, "\t\tpanic(%q)\n\t", "unexpected "+typeName)
|
||||
}
|
||||
}
|
||||
|
||||
func namedTypeFromTypeSwitch(stmt *ast.TypeSwitchStmt, info *types.Info) *types.Named {
|
||||
switch assign := stmt.Assign.(type) {
|
||||
case *ast.ExprStmt:
|
||||
if typ, ok := assign.X.(*ast.TypeAssertExpr); ok {
|
||||
if named, ok := info.TypeOf(typ.X).(*types.Named); ok {
|
||||
return named
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.AssignStmt:
|
||||
if typ, ok := assign.Rhs[0].(*ast.TypeAssertExpr); ok {
|
||||
if named, ok := info.TypeOf(typ.X).(*types.Named); ok {
|
||||
return named
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasDefaultCase(body *ast.BlockStmt) bool {
|
||||
for _, clause := range body.List {
|
||||
if len(clause.(*ast.CaseClause).List) == 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func caseConsts(body *ast.BlockStmt, info *types.Info) map[*types.Const]bool {
|
||||
out := map[*types.Const]bool{}
|
||||
for _, stmt := range body.List {
|
||||
for _, e := range stmt.(*ast.CaseClause).List {
|
||||
if info.Types[e].Value == nil {
|
||||
continue // not a constant
|
||||
}
|
||||
|
||||
if sel, ok := e.(*ast.SelectorExpr); ok {
|
||||
e = sel.Sel // replace pkg.C with C
|
||||
}
|
||||
|
||||
if e, ok := e.(*ast.Ident); ok {
|
||||
if c, ok := info.Uses[e].(*types.Const); ok {
|
||||
out[c] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
type caseType struct {
|
||||
named *types.Named
|
||||
ptr bool
|
||||
}
|
||||
|
||||
func caseTypes(body *ast.BlockStmt, info *types.Info) map[caseType]bool {
|
||||
out := map[caseType]bool{}
|
||||
for _, stmt := range body.List {
|
||||
for _, e := range stmt.(*ast.CaseClause).List {
|
||||
if tv, ok := info.Types[e]; ok && tv.IsType() {
|
||||
t := tv.Type
|
||||
ptr := false
|
||||
if p, ok := t.(*types.Pointer); ok {
|
||||
t = p.Elem()
|
||||
ptr = true
|
||||
}
|
||||
|
||||
if named, ok := t.(*types.Named); ok {
|
||||
out[caseType{named, ptr}] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// Copyright 2020 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 fillswitch_test
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
"golang.org/x/tools/gopls/internal/analysis/fillswitch"
|
||||
)
|
||||
|
||||
// analyzer allows us to test the fillswitch code action using the analysistest
|
||||
// harness.
|
||||
var analyzer = &analysis.Analyzer{
|
||||
Name: "fillswitch",
|
||||
Doc: "test only",
|
||||
Run: func(pass *analysis.Pass) (any, error) {
|
||||
for _, f := range pass.Files {
|
||||
for _, diag := range fillswitch.Diagnose(f, token.NoPos, token.NoPos, pass.Pkg, pass.TypesInfo) {
|
||||
pass.Report(diag)
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
},
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/fillswitch",
|
||||
RunDespiteErrors: true,
|
||||
}
|
||||
|
||||
func Test(t *testing.T) {
|
||||
testdata := analysistest.TestData()
|
||||
analysistest.Run(t, testdata, analyzer, "a")
|
||||
}
|
||||
Vendored
+78
@@ -0,0 +1,78 @@
|
||||
// Copyright 2020 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 fillswitch
|
||||
|
||||
import (
|
||||
data "b"
|
||||
)
|
||||
|
||||
type typeA int
|
||||
|
||||
const (
|
||||
typeAOne typeA = iota
|
||||
typeATwo
|
||||
typeAThree
|
||||
)
|
||||
|
||||
func doSwitch() {
|
||||
var a typeA
|
||||
switch a { // want `Add cases for typeA`
|
||||
}
|
||||
|
||||
switch a { // want `Add cases for typeA`
|
||||
case typeAOne:
|
||||
}
|
||||
|
||||
switch a {
|
||||
case typeAOne:
|
||||
default:
|
||||
}
|
||||
|
||||
switch a {
|
||||
case typeAOne:
|
||||
case typeATwo:
|
||||
case typeAThree:
|
||||
}
|
||||
|
||||
var b data.TypeB
|
||||
switch b { // want `Add cases for TypeB`
|
||||
case data.TypeBOne:
|
||||
}
|
||||
}
|
||||
|
||||
type notification interface {
|
||||
isNotification()
|
||||
}
|
||||
|
||||
type notificationOne struct{}
|
||||
|
||||
func (notificationOne) isNotification() {}
|
||||
|
||||
type notificationTwo struct{}
|
||||
|
||||
func (notificationTwo) isNotification() {}
|
||||
|
||||
func doTypeSwitch() {
|
||||
var not notification
|
||||
switch not.(type) { // want `Add cases for notification`
|
||||
}
|
||||
|
||||
switch not.(type) { // want `Add cases for notification`
|
||||
case notificationOne:
|
||||
}
|
||||
|
||||
switch not.(type) {
|
||||
case notificationOne:
|
||||
case notificationTwo:
|
||||
}
|
||||
|
||||
switch not.(type) {
|
||||
default:
|
||||
}
|
||||
|
||||
var t data.ExportedInterface
|
||||
switch t {
|
||||
}
|
||||
}
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
// Copyright 2020 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 fillswitch
|
||||
|
||||
type TypeB int
|
||||
|
||||
const (
|
||||
TypeBOne TypeB = iota
|
||||
TypeBTwo
|
||||
TypeBThree
|
||||
)
|
||||
|
||||
type ExportedInterface interface {
|
||||
isExportedInterface()
|
||||
}
|
||||
|
||||
type notExportedType struct{}
|
||||
|
||||
func (notExportedType) isExportedInterface() {}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
// 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 infertypeargs
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
"golang.org/x/tools/go/ast/inspector"
|
||||
"golang.org/x/tools/internal/typeparams"
|
||||
"golang.org/x/tools/internal/versions"
|
||||
)
|
||||
|
||||
const Doc = `check for unnecessary type arguments in call expressions
|
||||
|
||||
Explicit type arguments may be omitted from call expressions if they can be
|
||||
inferred from function arguments, or from other type arguments:
|
||||
|
||||
func f[T any](T) {}
|
||||
|
||||
func _() {
|
||||
f[string]("foo") // string could be inferred
|
||||
}
|
||||
`
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "infertypeargs",
|
||||
Doc: Doc,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
Run: run,
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/infertypeargs",
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
||||
for _, diag := range diagnose(pass.Fset, inspect, token.NoPos, token.NoPos, pass.Pkg, pass.TypesInfo) {
|
||||
pass.Report(diag)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Diagnose reports diagnostics describing simplifications to type
|
||||
// arguments overlapping with the provided start and end position.
|
||||
//
|
||||
// If start or end is token.NoPos, the corresponding bound is not checked
|
||||
// (i.e. if both start and end are NoPos, all call expressions are considered).
|
||||
func diagnose(fset *token.FileSet, inspect *inspector.Inspector, start, end token.Pos, pkg *types.Package, info *types.Info) []analysis.Diagnostic {
|
||||
var diags []analysis.Diagnostic
|
||||
|
||||
nodeFilter := []ast.Node{(*ast.CallExpr)(nil)}
|
||||
inspect.Preorder(nodeFilter, func(node ast.Node) {
|
||||
call := node.(*ast.CallExpr)
|
||||
x, lbrack, indices, rbrack := typeparams.UnpackIndexExpr(call.Fun)
|
||||
ident := calledIdent(x)
|
||||
if ident == nil || len(indices) == 0 {
|
||||
return // no explicit args, nothing to do
|
||||
}
|
||||
|
||||
if (start.IsValid() && call.End() < start) || (end.IsValid() && call.Pos() > end) {
|
||||
return // non-overlapping
|
||||
}
|
||||
|
||||
// Confirm that instantiation actually occurred at this ident.
|
||||
idata, ok := info.Instances[ident]
|
||||
if !ok {
|
||||
return // something went wrong, but fail open
|
||||
}
|
||||
instance := idata.Type
|
||||
|
||||
// Start removing argument expressions from the right, and check if we can
|
||||
// still infer the call expression.
|
||||
required := len(indices) // number of type expressions that are required
|
||||
for i := len(indices) - 1; i >= 0; i-- {
|
||||
var fun ast.Expr
|
||||
if i == 0 {
|
||||
// No longer an index expression: just use the parameterized operand.
|
||||
fun = x
|
||||
} else {
|
||||
fun = typeparams.PackIndexExpr(x, lbrack, indices[:i], indices[i-1].End())
|
||||
}
|
||||
newCall := &ast.CallExpr{
|
||||
Fun: fun,
|
||||
Lparen: call.Lparen,
|
||||
Args: call.Args,
|
||||
Ellipsis: call.Ellipsis,
|
||||
Rparen: call.Rparen,
|
||||
}
|
||||
info := &types.Info{
|
||||
Instances: make(map[*ast.Ident]types.Instance),
|
||||
}
|
||||
versions.InitFileVersions(info)
|
||||
if err := types.CheckExpr(fset, pkg, call.Pos(), newCall, info); err != nil {
|
||||
// Most likely inference failed.
|
||||
break
|
||||
}
|
||||
newIData := info.Instances[ident]
|
||||
newInstance := newIData.Type
|
||||
if !types.Identical(instance, newInstance) {
|
||||
// The inferred result type does not match the original result type, so
|
||||
// this simplification is not valid.
|
||||
break
|
||||
}
|
||||
required = i
|
||||
}
|
||||
if required < len(indices) {
|
||||
var s, e token.Pos
|
||||
var edit analysis.TextEdit
|
||||
if required == 0 {
|
||||
s, e = lbrack, rbrack+1 // erase the entire index
|
||||
edit = analysis.TextEdit{Pos: s, End: e}
|
||||
} else {
|
||||
s = indices[required].Pos()
|
||||
e = rbrack
|
||||
// erase from end of last arg to include last comma & white-spaces
|
||||
edit = analysis.TextEdit{Pos: indices[required-1].End(), End: e}
|
||||
}
|
||||
// Recheck that our (narrower) fixes overlap with the requested range.
|
||||
if (start.IsValid() && e < start) || (end.IsValid() && s > end) {
|
||||
return // non-overlapping
|
||||
}
|
||||
diags = append(diags, analysis.Diagnostic{
|
||||
Pos: s,
|
||||
End: e,
|
||||
Message: "unnecessary type arguments",
|
||||
SuggestedFixes: []analysis.SuggestedFix{{
|
||||
Message: "Simplify type arguments",
|
||||
TextEdits: []analysis.TextEdit{edit},
|
||||
}},
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
func calledIdent(x ast.Expr) *ast.Ident {
|
||||
switch x := x.(type) {
|
||||
case *ast.Ident:
|
||||
return x
|
||||
case *ast.SelectorExpr:
|
||||
return x.Sel
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// 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 infertypeargs_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
"golang.org/x/tools/gopls/internal/analysis/infertypeargs"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
testdata := analysistest.TestData()
|
||||
analysistest.RunWithSuggestedFixes(t, testdata, infertypeargs.Analyzer, "a")
|
||||
}
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
// 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.
|
||||
|
||||
// This file contains tests for the infertyepargs checker.
|
||||
|
||||
package a
|
||||
|
||||
func f[T any](T) {}
|
||||
|
||||
func g[T any]() T { var x T; return x }
|
||||
|
||||
func h[P interface{ ~*T }, T any]() {}
|
||||
|
||||
func _() {
|
||||
f[string]("hello") // want "unnecessary type arguments"
|
||||
f[int](2) // want "unnecessary type arguments"
|
||||
_ = g[int]()
|
||||
h[*int, int]() // want "unnecessary type arguments"
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// 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.
|
||||
|
||||
// This file contains tests for the infertyepargs checker.
|
||||
|
||||
package a
|
||||
|
||||
func f[T any](T) {}
|
||||
|
||||
func g[T any]() T { var x T; return x }
|
||||
|
||||
func h[P interface{ ~*T }, T any]() {}
|
||||
|
||||
func _() {
|
||||
f("hello") // want "unnecessary type arguments"
|
||||
f(2) // want "unnecessary type arguments"
|
||||
_ = g[int]()
|
||||
h[*int]() // want "unnecessary type arguments"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// 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 a
|
||||
|
||||
import "a/imported"
|
||||
|
||||
func _() {
|
||||
var x int
|
||||
imported.F[int](x) // want "unnecessary type arguments"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// 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 a
|
||||
|
||||
import "a/imported"
|
||||
|
||||
func _() {
|
||||
var x int
|
||||
imported.F(x) // want "unnecessary type arguments"
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// 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 imported
|
||||
|
||||
func F[T any](T) {}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// 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.
|
||||
|
||||
// We should not suggest removing type arguments if doing so would change the
|
||||
// resulting type.
|
||||
|
||||
package a
|
||||
|
||||
func id[T any](t T) T { return t }
|
||||
|
||||
var _ = id[int](1) // want "unnecessary type arguments"
|
||||
var _ = id[string]("foo") // want "unnecessary type arguments"
|
||||
var _ = id[int64](2)
|
||||
|
||||
func pair[T any](t T) (T, T) { return t, t }
|
||||
|
||||
var _, _ = pair[int](3) // want "unnecessary type arguments"
|
||||
var _, _ = pair[int64](3)
|
||||
|
||||
func noreturn[T any](t T) {}
|
||||
|
||||
func _() {
|
||||
noreturn[int64](4)
|
||||
noreturn[int](4) // want "unnecessary type arguments"
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// 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.
|
||||
|
||||
// We should not suggest removing type arguments if doing so would change the
|
||||
// resulting type.
|
||||
|
||||
package a
|
||||
|
||||
func id[T any](t T) T { return t }
|
||||
|
||||
var _ = id(1) // want "unnecessary type arguments"
|
||||
var _ = id("foo") // want "unnecessary type arguments"
|
||||
var _ = id[int64](2)
|
||||
|
||||
func pair[T any](t T) (T, T) { return t, t }
|
||||
|
||||
var _, _ = pair(3) // want "unnecessary type arguments"
|
||||
var _, _ = pair[int64](3)
|
||||
|
||||
func noreturn[T any](t T) {}
|
||||
|
||||
func _() {
|
||||
noreturn[int64](4)
|
||||
noreturn(4) // want "unnecessary type arguments"
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2020 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 nonewvars defines an Analyzer that applies suggested fixes
|
||||
// to errors of the type "no new variables on left side of :=".
|
||||
//
|
||||
// # Analyzer nonewvars
|
||||
//
|
||||
// nonewvars: suggested fixes for "no new vars on left side of :="
|
||||
//
|
||||
// This checker provides suggested fixes for type errors of the
|
||||
// type "no new vars on left side of :=". For example:
|
||||
//
|
||||
// z := 1
|
||||
// z := 2
|
||||
//
|
||||
// will turn into
|
||||
//
|
||||
// z := 1
|
||||
// z = 2
|
||||
package nonewvars
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright 2020 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 nonewvars defines an Analyzer that applies suggested fixes
|
||||
// to errors of the type "no new variables on left side of :=".
|
||||
package nonewvars
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/token"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
"golang.org/x/tools/go/ast/inspector"
|
||||
"golang.org/x/tools/internal/analysisinternal"
|
||||
)
|
||||
|
||||
//go:embed doc.go
|
||||
var doc string
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "nonewvars",
|
||||
Doc: analysisinternal.MustExtractDoc(doc, "nonewvars"),
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
Run: run,
|
||||
RunDespiteErrors: true,
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/nonewvars",
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (interface{}, error) {
|
||||
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
||||
if len(pass.TypeErrors) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
nodeFilter := []ast.Node{(*ast.AssignStmt)(nil)}
|
||||
inspect.Preorder(nodeFilter, func(n ast.Node) {
|
||||
assignStmt, _ := n.(*ast.AssignStmt)
|
||||
// We only care about ":=".
|
||||
if assignStmt.Tok != token.DEFINE {
|
||||
return
|
||||
}
|
||||
|
||||
var file *ast.File
|
||||
for _, f := range pass.Files {
|
||||
if f.Pos() <= assignStmt.Pos() && assignStmt.Pos() < f.End() {
|
||||
file = f
|
||||
break
|
||||
}
|
||||
}
|
||||
if file == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, err := range pass.TypeErrors {
|
||||
if !FixesError(err.Msg) {
|
||||
continue
|
||||
}
|
||||
if assignStmt.Pos() > err.Pos || err.Pos >= assignStmt.End() {
|
||||
continue
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := format.Node(&buf, pass.Fset, file); err != nil {
|
||||
continue
|
||||
}
|
||||
pass.Report(analysis.Diagnostic{
|
||||
Pos: err.Pos,
|
||||
End: analysisinternal.TypeErrorEndPos(pass.Fset, buf.Bytes(), err.Pos),
|
||||
Message: err.Msg,
|
||||
SuggestedFixes: []analysis.SuggestedFix{{
|
||||
Message: "Change ':=' to '='",
|
||||
TextEdits: []analysis.TextEdit{{
|
||||
Pos: err.Pos,
|
||||
End: err.Pos + 1,
|
||||
}},
|
||||
}},
|
||||
})
|
||||
}
|
||||
})
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func FixesError(msg string) bool {
|
||||
return msg == "no new variables on left side of :="
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// Copyright 2020 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 nonewvars_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
"golang.org/x/tools/gopls/internal/analysis/nonewvars"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
testdata := analysistest.TestData()
|
||||
analysistest.RunWithSuggestedFixes(t, testdata, nonewvars.Analyzer, "a", "typeparams")
|
||||
}
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
// Copyright 2020 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 nonewvars
|
||||
|
||||
import "log"
|
||||
|
||||
func x() {
|
||||
z := 1
|
||||
z := 2 // want "no new variables on left side of :="
|
||||
|
||||
_, z := 3, 100 // want "no new variables on left side of :="
|
||||
|
||||
log.Println(z)
|
||||
}
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
// Copyright 2020 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 nonewvars
|
||||
|
||||
import "log"
|
||||
|
||||
func x() {
|
||||
z := 1
|
||||
z = 2 // want "no new variables on left side of :="
|
||||
|
||||
_, z = 3, 100 // want "no new variables on left side of :="
|
||||
|
||||
log.Println(z)
|
||||
}
|
||||
go/pkg/mod/golang.org/x/tools/gopls@v0.16.2/internal/analysis/nonewvars/testdata/src/typeparams/a.go
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
package nonewvars
|
||||
|
||||
func hello[T any]() int {
|
||||
var z T
|
||||
z := 1 // want "no new variables on left side of :="
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package nonewvars
|
||||
|
||||
func hello[T any]() int {
|
||||
var z T
|
||||
z = 1 // want "no new variables on left side of :="
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
// Copyright 2024 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 norangeoverfunc
|
||||
|
||||
// TODO(adonovan): delete this when #67237 and dominikh/go-tools#1494 are fixed.
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
"golang.org/x/tools/go/ast/inspector"
|
||||
)
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "norangeoverfunc",
|
||||
Doc: `norangeoverfunc fails if a package uses go1.23 range-over-func
|
||||
|
||||
Require it from any analyzer that cannot yet safely process this new feature.`,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
Run: run,
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
||||
filter := []ast.Node{(*ast.RangeStmt)(nil)}
|
||||
|
||||
// TODO(adonovan): opt: short circuit if not using go1.23.
|
||||
|
||||
var found *ast.RangeStmt
|
||||
inspect.Preorder(filter, func(n ast.Node) {
|
||||
if found == nil {
|
||||
stmt := n.(*ast.RangeStmt)
|
||||
if _, ok := pass.TypesInfo.TypeOf(stmt.X).Underlying().(*types.Signature); ok {
|
||||
found = stmt
|
||||
}
|
||||
}
|
||||
})
|
||||
if found != nil {
|
||||
return nil, fmt.Errorf("package %q uses go1.23 range-over-func; cannot build SSA or IR (#67237)",
|
||||
pass.Pkg.Path())
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2023 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 noresultvalues defines an Analyzer that applies suggested fixes
|
||||
// to errors of the type "no result values expected".
|
||||
//
|
||||
// # Analyzer noresultvalues
|
||||
//
|
||||
// noresultvalues: suggested fixes for unexpected return values
|
||||
//
|
||||
// This checker provides suggested fixes for type errors of the
|
||||
// type "no result values expected" or "too many return values".
|
||||
// For example:
|
||||
//
|
||||
// func z() { return nil }
|
||||
//
|
||||
// will turn into
|
||||
//
|
||||
// func z() { return }
|
||||
package noresultvalues
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
// Copyright 2020 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 noresultvalues
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"strings"
|
||||
|
||||
_ "embed"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
"golang.org/x/tools/go/ast/inspector"
|
||||
"golang.org/x/tools/internal/analysisinternal"
|
||||
)
|
||||
|
||||
//go:embed doc.go
|
||||
var doc string
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "noresultvalues",
|
||||
Doc: analysisinternal.MustExtractDoc(doc, "noresultvalues"),
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
Run: run,
|
||||
RunDespiteErrors: true,
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/noresultvalues",
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (interface{}, error) {
|
||||
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
||||
if len(pass.TypeErrors) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
nodeFilter := []ast.Node{(*ast.ReturnStmt)(nil)}
|
||||
inspect.Preorder(nodeFilter, func(n ast.Node) {
|
||||
retStmt, _ := n.(*ast.ReturnStmt)
|
||||
|
||||
var file *ast.File
|
||||
for _, f := range pass.Files {
|
||||
if f.Pos() <= retStmt.Pos() && retStmt.Pos() < f.End() {
|
||||
file = f
|
||||
break
|
||||
}
|
||||
}
|
||||
if file == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, err := range pass.TypeErrors {
|
||||
if !FixesError(err.Msg) {
|
||||
continue
|
||||
}
|
||||
if retStmt.Pos() >= err.Pos || err.Pos >= retStmt.End() {
|
||||
continue
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := format.Node(&buf, pass.Fset, file); err != nil {
|
||||
continue
|
||||
}
|
||||
pass.Report(analysis.Diagnostic{
|
||||
Pos: err.Pos,
|
||||
End: analysisinternal.TypeErrorEndPos(pass.Fset, buf.Bytes(), err.Pos),
|
||||
Message: err.Msg,
|
||||
SuggestedFixes: []analysis.SuggestedFix{{
|
||||
Message: "Delete return values",
|
||||
TextEdits: []analysis.TextEdit{{
|
||||
Pos: retStmt.Pos(),
|
||||
End: retStmt.End(),
|
||||
NewText: []byte("return"),
|
||||
}},
|
||||
}},
|
||||
})
|
||||
}
|
||||
})
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func FixesError(msg string) bool {
|
||||
return msg == "no result values expected" ||
|
||||
strings.HasPrefix(msg, "too many return values") && strings.Contains(msg, "want ()")
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// Copyright 2020 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 noresultvalues_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
"golang.org/x/tools/gopls/internal/analysis/noresultvalues"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
testdata := analysistest.TestData()
|
||||
analysistest.RunWithSuggestedFixes(t, testdata, noresultvalues.Analyzer, "a", "typeparams")
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
// Copyright 2020 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 noresultvalues
|
||||
|
||||
func x() { return nil } // want `no result values expected|too many return values`
|
||||
|
||||
func y() { return nil, "hello" } // want `no result values expected|too many return values`
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// Copyright 2020 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 noresultvalues
|
||||
|
||||
func x() { return } // want `no result values expected|too many return values`
|
||||
|
||||
func y() { return } // want `no result values expected|too many return values`
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package noresult
|
||||
|
||||
func hello[T any]() {
|
||||
var z T
|
||||
return z // want `no result values expected|too many return values`
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package noresult
|
||||
|
||||
func hello[T any]() {
|
||||
var z T
|
||||
return // want `no result values expected|too many return values`
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// Copyright 2023 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 simplifycompositelit defines an Analyzer that simplifies composite literals.
|
||||
// https://github.com/golang/go/blob/master/src/cmd/gofmt/simplify.go
|
||||
// https://golang.org/cmd/gofmt/#hdr-The_simplify_command
|
||||
//
|
||||
// # Analyzer simplifycompositelit
|
||||
//
|
||||
// simplifycompositelit: check for composite literal simplifications
|
||||
//
|
||||
// An array, slice, or map composite literal of the form:
|
||||
//
|
||||
// []T{T{}, T{}}
|
||||
//
|
||||
// will be simplified to:
|
||||
//
|
||||
// []T{{}, {}}
|
||||
//
|
||||
// This is one of the simplifications that "gofmt -s" applies.
|
||||
package simplifycompositelit
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
// Copyright 2020 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 simplifycompositelit defines an Analyzer that simplifies composite literals.
|
||||
// https://github.com/golang/go/blob/master/src/cmd/gofmt/simplify.go
|
||||
// https://golang.org/cmd/gofmt/#hdr-The_simplify_command
|
||||
package simplifycompositelit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/printer"
|
||||
"go/token"
|
||||
"reflect"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
"golang.org/x/tools/go/ast/inspector"
|
||||
"golang.org/x/tools/internal/analysisinternal"
|
||||
)
|
||||
|
||||
//go:embed doc.go
|
||||
var doc string
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "simplifycompositelit",
|
||||
Doc: analysisinternal.MustExtractDoc(doc, "simplifycompositelit"),
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
Run: run,
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifycompositelit",
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (interface{}, error) {
|
||||
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
||||
nodeFilter := []ast.Node{(*ast.CompositeLit)(nil)}
|
||||
inspect.Preorder(nodeFilter, func(n ast.Node) {
|
||||
expr := n.(*ast.CompositeLit)
|
||||
|
||||
outer := expr
|
||||
var keyType, eltType ast.Expr
|
||||
switch typ := outer.Type.(type) {
|
||||
case *ast.ArrayType:
|
||||
eltType = typ.Elt
|
||||
case *ast.MapType:
|
||||
keyType = typ.Key
|
||||
eltType = typ.Value
|
||||
}
|
||||
|
||||
if eltType == nil {
|
||||
return
|
||||
}
|
||||
var ktyp reflect.Value
|
||||
if keyType != nil {
|
||||
ktyp = reflect.ValueOf(keyType)
|
||||
}
|
||||
typ := reflect.ValueOf(eltType)
|
||||
for _, x := range outer.Elts {
|
||||
// look at value of indexed/named elements
|
||||
if t, ok := x.(*ast.KeyValueExpr); ok {
|
||||
if keyType != nil {
|
||||
simplifyLiteral(pass, ktyp, keyType, t.Key)
|
||||
}
|
||||
x = t.Value
|
||||
}
|
||||
simplifyLiteral(pass, typ, eltType, x)
|
||||
}
|
||||
})
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func simplifyLiteral(pass *analysis.Pass, typ reflect.Value, astType, x ast.Expr) {
|
||||
// if the element is a composite literal and its literal type
|
||||
// matches the outer literal's element type exactly, the inner
|
||||
// literal type may be omitted
|
||||
if inner, ok := x.(*ast.CompositeLit); ok && match(typ, reflect.ValueOf(inner.Type)) {
|
||||
var b bytes.Buffer
|
||||
printer.Fprint(&b, pass.Fset, inner.Type)
|
||||
createDiagnostic(pass, inner.Type.Pos(), inner.Type.End(), b.String())
|
||||
}
|
||||
// if the outer literal's element type is a pointer type *T
|
||||
// and the element is & of a composite literal of type T,
|
||||
// the inner &T may be omitted.
|
||||
if ptr, ok := astType.(*ast.StarExpr); ok {
|
||||
if addr, ok := x.(*ast.UnaryExpr); ok && addr.Op == token.AND {
|
||||
if inner, ok := addr.X.(*ast.CompositeLit); ok {
|
||||
if match(reflect.ValueOf(ptr.X), reflect.ValueOf(inner.Type)) {
|
||||
var b bytes.Buffer
|
||||
printer.Fprint(&b, pass.Fset, inner.Type)
|
||||
// Account for the & by subtracting 1 from typ.Pos().
|
||||
createDiagnostic(pass, inner.Type.Pos()-1, inner.Type.End(), "&"+b.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createDiagnostic(pass *analysis.Pass, start, end token.Pos, typ string) {
|
||||
pass.Report(analysis.Diagnostic{
|
||||
Pos: start,
|
||||
End: end,
|
||||
Message: "redundant type from array, slice, or map composite literal",
|
||||
SuggestedFixes: []analysis.SuggestedFix{{
|
||||
Message: fmt.Sprintf("Remove '%s'", typ),
|
||||
TextEdits: []analysis.TextEdit{{
|
||||
Pos: start,
|
||||
End: end,
|
||||
NewText: []byte{},
|
||||
}},
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
// match reports whether pattern matches val,
|
||||
// recording wildcard submatches in m.
|
||||
// If m == nil, match checks whether pattern == val.
|
||||
// from https://github.com/golang/go/blob/26154f31ad6c801d8bad5ef58df1e9263c6beec7/src/cmd/gofmt/rewrite.go#L160
|
||||
func match(pattern, val reflect.Value) bool {
|
||||
// Otherwise, pattern and val must match recursively.
|
||||
if !pattern.IsValid() || !val.IsValid() {
|
||||
return !pattern.IsValid() && !val.IsValid()
|
||||
}
|
||||
if pattern.Type() != val.Type() {
|
||||
return false
|
||||
}
|
||||
|
||||
// Special cases.
|
||||
switch pattern.Type() {
|
||||
case identType:
|
||||
// For identifiers, only the names need to match
|
||||
// (and none of the other *ast.Object information).
|
||||
// This is a common case, handle it all here instead
|
||||
// of recursing down any further via reflection.
|
||||
p := pattern.Interface().(*ast.Ident)
|
||||
v := val.Interface().(*ast.Ident)
|
||||
return p == nil && v == nil || p != nil && v != nil && p.Name == v.Name
|
||||
case objectPtrType, positionType:
|
||||
// object pointers and token positions always match
|
||||
return true
|
||||
case callExprType:
|
||||
// For calls, the Ellipsis fields (token.Position) must
|
||||
// match since that is how f(x) and f(x...) are different.
|
||||
// Check them here but fall through for the remaining fields.
|
||||
p := pattern.Interface().(*ast.CallExpr)
|
||||
v := val.Interface().(*ast.CallExpr)
|
||||
if p.Ellipsis.IsValid() != v.Ellipsis.IsValid() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
p := reflect.Indirect(pattern)
|
||||
v := reflect.Indirect(val)
|
||||
if !p.IsValid() || !v.IsValid() {
|
||||
return !p.IsValid() && !v.IsValid()
|
||||
}
|
||||
|
||||
switch p.Kind() {
|
||||
case reflect.Slice:
|
||||
if p.Len() != v.Len() {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < p.Len(); i++ {
|
||||
if !match(p.Index(i), v.Index(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
case reflect.Struct:
|
||||
for i := 0; i < p.NumField(); i++ {
|
||||
if !match(p.Field(i), v.Field(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
case reflect.Interface:
|
||||
return match(p.Elem(), v.Elem())
|
||||
}
|
||||
|
||||
// Handle token integers, etc.
|
||||
return p.Interface() == v.Interface()
|
||||
}
|
||||
|
||||
// Values/types for special cases.
|
||||
var (
|
||||
identType = reflect.TypeOf((*ast.Ident)(nil))
|
||||
objectPtrType = reflect.TypeOf((*ast.Object)(nil))
|
||||
positionType = reflect.TypeOf(token.NoPos)
|
||||
callExprType = reflect.TypeOf((*ast.CallExpr)(nil))
|
||||
)
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// Copyright 2020 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 simplifycompositelit_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
"golang.org/x/tools/gopls/internal/analysis/simplifycompositelit"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
testdata := analysistest.TestData()
|
||||
analysistest.RunWithSuggestedFixes(t, testdata, simplifycompositelit.Analyzer, "a")
|
||||
}
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
// Copyright 2020 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 testdata
|
||||
|
||||
type T struct {
|
||||
x, y int
|
||||
}
|
||||
|
||||
type T2 struct {
|
||||
w, z int
|
||||
}
|
||||
|
||||
var _ = [42]T{
|
||||
T{}, // want "redundant type from array, slice, or map composite literal"
|
||||
T{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
T{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = [...]T{
|
||||
T{}, // want "redundant type from array, slice, or map composite literal"
|
||||
T{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
T{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []T{
|
||||
T{}, // want "redundant type from array, slice, or map composite literal"
|
||||
T{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
T{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []T{
|
||||
T{}, // want "redundant type from array, slice, or map composite literal"
|
||||
10: T{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
20: T{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []struct {
|
||||
x, y int
|
||||
}{
|
||||
struct{ x, y int }{}, // want "redundant type from array, slice, or map composite literal"
|
||||
10: struct{ x, y int }{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
20: struct{ x, y int }{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []interface{}{
|
||||
T{},
|
||||
10: T{1, 2},
|
||||
20: T{3, 4},
|
||||
}
|
||||
|
||||
var _ = [][]int{
|
||||
[]int{}, // want "redundant type from array, slice, or map composite literal"
|
||||
[]int{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
[]int{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = [][]int{
|
||||
([]int{}),
|
||||
([]int{1, 2}),
|
||||
[]int{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = [][][]int{
|
||||
[][]int{}, // want "redundant type from array, slice, or map composite literal"
|
||||
[][]int{ // want "redundant type from array, slice, or map composite literal"
|
||||
[]int{}, // want "redundant type from array, slice, or map composite literal"
|
||||
[]int{0, 1, 2, 3}, // want "redundant type from array, slice, or map composite literal"
|
||||
[]int{4, 5}, // want "redundant type from array, slice, or map composite literal"
|
||||
},
|
||||
}
|
||||
|
||||
var _ = map[string]T{
|
||||
"foo": T{}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bar": T{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bal": T{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[string]struct {
|
||||
x, y int
|
||||
}{
|
||||
"foo": struct{ x, y int }{}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bar": struct{ x, y int }{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bal": struct{ x, y int }{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[string]interface{}{
|
||||
"foo": T{},
|
||||
"bar": T{1, 2},
|
||||
"bal": T{3, 4},
|
||||
}
|
||||
|
||||
var _ = map[string][]int{
|
||||
"foo": []int{}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bar": []int{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bal": []int{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[string][]int{
|
||||
"foo": ([]int{}),
|
||||
"bar": ([]int{1, 2}),
|
||||
"bal": []int{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
type Point struct {
|
||||
a int
|
||||
b int
|
||||
}
|
||||
|
||||
type Piece struct {
|
||||
a int
|
||||
b int
|
||||
c Point
|
||||
d []Point
|
||||
e *Point
|
||||
f *Point
|
||||
}
|
||||
|
||||
// from exp/4s/data.go
|
||||
var pieces3 = []Piece{
|
||||
Piece{0, 0, Point{4, 1}, []Point{Point{0, 0}, Point{1, 0}, Point{1, 0}, Point{1, 0}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
Piece{1, 0, Point{1, 4}, []Point{Point{0, 0}, Point{0, 1}, Point{0, 1}, Point{0, 1}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
Piece{2, 0, Point{4, 1}, []Point{Point{0, 0}, Point{1, 0}, Point{1, 0}, Point{1, 0}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
Piece{3, 0, Point{1, 4}, []Point{Point{0, 0}, Point{0, 1}, Point{0, 1}, Point{0, 1}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = [42]*T{
|
||||
&T{}, // want "redundant type from array, slice, or map composite literal"
|
||||
&T{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
&T{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = [...]*T{
|
||||
&T{}, // want "redundant type from array, slice, or map composite literal"
|
||||
&T{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
&T{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []*T{
|
||||
&T{}, // want "redundant type from array, slice, or map composite literal"
|
||||
&T{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
&T{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []*T{
|
||||
&T{}, // want "redundant type from array, slice, or map composite literal"
|
||||
10: &T{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
20: &T{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []*struct {
|
||||
x, y int
|
||||
}{
|
||||
&struct{ x, y int }{}, // want "redundant type from array, slice, or map composite literal"
|
||||
10: &struct{ x, y int }{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
20: &struct{ x, y int }{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []interface{}{
|
||||
&T{},
|
||||
10: &T{1, 2},
|
||||
20: &T{3, 4},
|
||||
}
|
||||
|
||||
var _ = []*[]int{
|
||||
&[]int{}, // want "redundant type from array, slice, or map composite literal"
|
||||
&[]int{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
&[]int{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []*[]int{
|
||||
(&[]int{}),
|
||||
(&[]int{1, 2}),
|
||||
&[]int{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []*[]*[]int{
|
||||
&[]*[]int{}, // want "redundant type from array, slice, or map composite literal"
|
||||
&[]*[]int{ // want "redundant type from array, slice, or map composite literal"
|
||||
&[]int{}, // want "redundant type from array, slice, or map composite literal"
|
||||
&[]int{0, 1, 2, 3}, // want "redundant type from array, slice, or map composite literal"
|
||||
&[]int{4, 5}, // want "redundant type from array, slice, or map composite literal"
|
||||
},
|
||||
}
|
||||
|
||||
var _ = map[string]*T{
|
||||
"foo": &T{}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bar": &T{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bal": &T{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[string]*struct {
|
||||
x, y int
|
||||
}{
|
||||
"foo": &struct{ x, y int }{}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bar": &struct{ x, y int }{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bal": &struct{ x, y int }{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[string]interface{}{
|
||||
"foo": &T{},
|
||||
"bar": &T{1, 2},
|
||||
"bal": &T{3, 4},
|
||||
}
|
||||
|
||||
var _ = map[string]*[]int{
|
||||
"foo": &[]int{}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bar": &[]int{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bal": &[]int{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[string]*[]int{
|
||||
"foo": (&[]int{}),
|
||||
"bar": (&[]int{1, 2}),
|
||||
"bal": &[]int{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var pieces4 = []*Piece{
|
||||
&Piece{0, 0, Point{4, 1}, []Point{Point{0, 0}, Point{1, 0}, Point{1, 0}, Point{1, 0}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
&Piece{1, 0, Point{1, 4}, []Point{Point{0, 0}, Point{0, 1}, Point{0, 1}, Point{0, 1}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
&Piece{2, 0, Point{4, 1}, []Point{Point{0, 0}, Point{1, 0}, Point{1, 0}, Point{1, 0}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
&Piece{3, 0, Point{1, 4}, []Point{Point{0, 0}, Point{0, 1}, Point{0, 1}, Point{0, 1}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[T]T2{
|
||||
T{1, 2}: T2{3, 4}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
T{5, 6}: T2{7, 8}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[*T]*T2{
|
||||
&T{1, 2}: &T2{3, 4}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
&T{5, 6}: &T2{7, 8}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
// Copyright 2020 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 testdata
|
||||
|
||||
type T struct {
|
||||
x, y int
|
||||
}
|
||||
|
||||
type T2 struct {
|
||||
w, z int
|
||||
}
|
||||
|
||||
var _ = [42]T{
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = [...]T{
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []T{
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []T{
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
10: {1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
20: {3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []struct {
|
||||
x, y int
|
||||
}{
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
10: {1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
20: {3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []interface{}{
|
||||
T{},
|
||||
10: T{1, 2},
|
||||
20: T{3, 4},
|
||||
}
|
||||
|
||||
var _ = [][]int{
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = [][]int{
|
||||
([]int{}),
|
||||
([]int{1, 2}),
|
||||
{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = [][][]int{
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
{ // want "redundant type from array, slice, or map composite literal"
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
{0, 1, 2, 3}, // want "redundant type from array, slice, or map composite literal"
|
||||
{4, 5}, // want "redundant type from array, slice, or map composite literal"
|
||||
},
|
||||
}
|
||||
|
||||
var _ = map[string]T{
|
||||
"foo": {}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bar": {1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bal": {3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[string]struct {
|
||||
x, y int
|
||||
}{
|
||||
"foo": {}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bar": {1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bal": {3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[string]interface{}{
|
||||
"foo": T{},
|
||||
"bar": T{1, 2},
|
||||
"bal": T{3, 4},
|
||||
}
|
||||
|
||||
var _ = map[string][]int{
|
||||
"foo": {}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bar": {1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bal": {3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[string][]int{
|
||||
"foo": ([]int{}),
|
||||
"bar": ([]int{1, 2}),
|
||||
"bal": {3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
type Point struct {
|
||||
a int
|
||||
b int
|
||||
}
|
||||
|
||||
type Piece struct {
|
||||
a int
|
||||
b int
|
||||
c Point
|
||||
d []Point
|
||||
e *Point
|
||||
f *Point
|
||||
}
|
||||
|
||||
// from exp/4s/data.go
|
||||
var pieces3 = []Piece{
|
||||
{0, 0, Point{4, 1}, []Point{{0, 0}, {1, 0}, {1, 0}, {1, 0}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
{1, 0, Point{1, 4}, []Point{{0, 0}, {0, 1}, {0, 1}, {0, 1}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
{2, 0, Point{4, 1}, []Point{{0, 0}, {1, 0}, {1, 0}, {1, 0}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
{3, 0, Point{1, 4}, []Point{{0, 0}, {0, 1}, {0, 1}, {0, 1}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = [42]*T{
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = [...]*T{
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []*T{
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []*T{
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
10: {1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
20: {3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []*struct {
|
||||
x, y int
|
||||
}{
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
10: {1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
20: {3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []interface{}{
|
||||
&T{},
|
||||
10: &T{1, 2},
|
||||
20: &T{3, 4},
|
||||
}
|
||||
|
||||
var _ = []*[]int{
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []*[]int{
|
||||
(&[]int{}),
|
||||
(&[]int{1, 2}),
|
||||
{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []*[]*[]int{
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
{ // want "redundant type from array, slice, or map composite literal"
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
{0, 1, 2, 3}, // want "redundant type from array, slice, or map composite literal"
|
||||
{4, 5}, // want "redundant type from array, slice, or map composite literal"
|
||||
},
|
||||
}
|
||||
|
||||
var _ = map[string]*T{
|
||||
"foo": {}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bar": {1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bal": {3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[string]*struct {
|
||||
x, y int
|
||||
}{
|
||||
"foo": {}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bar": {1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bal": {3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[string]interface{}{
|
||||
"foo": &T{},
|
||||
"bar": &T{1, 2},
|
||||
"bal": &T{3, 4},
|
||||
}
|
||||
|
||||
var _ = map[string]*[]int{
|
||||
"foo": {}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bar": {1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bal": {3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[string]*[]int{
|
||||
"foo": (&[]int{}),
|
||||
"bar": (&[]int{1, 2}),
|
||||
"bal": {3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var pieces4 = []*Piece{
|
||||
{0, 0, Point{4, 1}, []Point{{0, 0}, {1, 0}, {1, 0}, {1, 0}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
{1, 0, Point{1, 4}, []Point{{0, 0}, {0, 1}, {0, 1}, {0, 1}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
{2, 0, Point{4, 1}, []Point{{0, 0}, {1, 0}, {1, 0}, {1, 0}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
{3, 0, Point{1, 4}, []Point{{0, 0}, {0, 1}, {0, 1}, {0, 1}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[T]T2{
|
||||
{1, 2}: {3, 4}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
{5, 6}: {7, 8}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[*T]*T2{
|
||||
{1, 2}: {3, 4}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
{5, 6}: {7, 8}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2023 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 simplifyrange defines an Analyzer that simplifies range statements.
|
||||
// https://golang.org/cmd/gofmt/#hdr-The_simplify_command
|
||||
// https://github.com/golang/go/blob/master/src/cmd/gofmt/simplify.go
|
||||
//
|
||||
// # Analyzer simplifyrange
|
||||
//
|
||||
// simplifyrange: check for range statement simplifications
|
||||
//
|
||||
// A range of the form:
|
||||
//
|
||||
// for x, _ = range v {...}
|
||||
//
|
||||
// will be simplified to:
|
||||
//
|
||||
// for x = range v {...}
|
||||
//
|
||||
// A range of the form:
|
||||
//
|
||||
// for _ = range v {...}
|
||||
//
|
||||
// will be simplified to:
|
||||
//
|
||||
// for range v {...}
|
||||
//
|
||||
// This is one of the simplifications that "gofmt -s" applies.
|
||||
package simplifyrange
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user